pkg/application/dao/dao.go
// Copyright © 2023 Horizoncd.//// 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 dao import ( "context" "fmt" "reflect" "time" "gorm.io/gorm" corecommon "github.com/horizoncd/horizon/core/common" herrors "github.com/horizoncd/horizon/core/errors" "github.com/horizoncd/horizon/lib/q" "github.com/horizoncd/horizon/pkg/application/models" "github.com/horizoncd/horizon/pkg/common" groupmodels "github.com/horizoncd/horizon/pkg/group/models" membermodels "github.com/horizoncd/horizon/pkg/member/models" "github.com/horizoncd/horizon/pkg/rbac/role" usermodels "github.com/horizoncd/horizon/pkg/user/models") const ( KeyTemplate = "template" KeyTemplateRelease = "templateRelease") type DAO interface { GetByID(ctx context.Context, id uint, includeSoftDelete bool) (*models.Application, error) GetByIDs(ctx context.Context, ids []uint) ([]*models.Application, error) GetByGroupIDs(ctx context.Context, groupIDs []uint) ([]*models.Application, error) GetByName(ctx context.Context, name string) (*models.Application, error) GetByNamesUnderGroup(ctx context.Context, groupID uint, names []string) ([]*models.Application, error) // GetByNameFuzzily get applications that fuzzily matching the given name GetByNameFuzzily(ctx context.Context, name string, includeSoftDelete bool) ([]*models.Application, error) // CountByGroupID get the count of the records matching the given groupID CountByGroupID(ctx context.Context, groupID uint) (int64, error) Create(ctx context.Context, application *models.Application, extraMembers map[*usermodels.User]string) (*models.Application, error) UpdateByID(ctx context.Context, id uint, application *models.Application) (*models.Application, error) DeleteByID(ctx context.Context, id uint) error TransferByID(ctx context.Context, id uint, groupID uint) error List(ctx context.Context, groupIDs []uint, query *q.Query) (int, []*models.Application, error)} // NewDAO returns an instance of the default DAOfunc NewDAO(db *gorm.DB) DAO { return &dao{db: db}} type dao struct{ db *gorm.DB } func (d *dao) CountByGroupID(ctx context.Context, groupID uint) (int64, error) { var count int64 result := d.db.WithContext(ctx).Raw(common.ApplicationCountByGroupID, groupID).Scan(&count) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return 0, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return 0, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return count, result.Error} Similar blocks of code found in 2 locations. Consider refactoring.func (d *dao) GetByNameFuzzily(ctx context.Context, name string, includeSoftDelete bool) ([]*models.Application, error) { var applications []*models.Application statement := d.db.Unscoped().WithContext(ctx).Where("name like ?", fmt.Sprintf("%%%s%%", name)) if !includeSoftDelete { statement.Where("deleted_ts = 0") } result := statement.Find(&applications) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return nil, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return applications, result.Error} func (d *dao) GetByID(ctx context.Context, id uint, includeSoftDelete bool) (*models.Application, error) { var application models.Application statement := d.db.Unscoped().WithContext(ctx).Where("id = ?", id) if !includeSoftDelete { statement.Where("deleted_ts = 0") } result := statement.First(&application) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return nil, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return &application, result.Error} func (d *dao) GetByIDs(ctx context.Context, ids []uint) ([]*models.Application, error) { var applications []*models.Application result := d.db.WithContext(ctx).Raw(common.ApplicationQueryByIDs, ids).Scan(&applications) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return applications, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return applications, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return applications, nil} func (d *dao) GetByGroupIDs(ctx context.Context, groupIDs []uint) ([]*models.Application, error) { var applications []*models.Application result := d.db.WithContext(ctx).Raw(common.ApplicationQueryByGroupIDs, groupIDs).Scan(&applications) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return applications, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return applications, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return applications, result.Error} func (d *dao) GetByName(ctx context.Context, name string) (*models.Application, error) { var application models.Application result := d.db.WithContext(ctx).Raw(common.ApplicationQueryByName, name).First(&application) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return &application, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return &application, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return &application, result.Error} func (d *dao) GetByNamesUnderGroup(ctx context.Context, groupID uint, names []string) ([]*models.Application, error) { var applications []*models.Application result := d.db.WithContext(ctx).Raw(common.ApplicationQueryByNamesUnderGroup, groupID, names).Scan(&applications) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return applications, herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return applications, herrors.NewErrGetFailed(herrors.ApplicationInDB, result.Error.Error()) } return applications, result.Error} Method `dao.Create` has 5 return statements (exceeds 4 allowed).func (d *dao) Create(ctx context.Context, application *models.Application, extraMembers map[*usermodels.User]string) (*models.Application, error) { err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // TODO: check the group exist if err := tx.Create(application).Error; err != nil { return herrors.NewErrInsertFailed(herrors.ApplicationInDB, err.Error()) } // insert records to member table members := make([]*membermodels.Member, 0) // the owner who created this application members = append(members, &membermodels.Member{ ResourceType: membermodels.TypeApplication, ResourceID: application.ID, Role: role.Owner, MemberType: membermodels.MemberUser, MemberNameID: application.CreatedBy, GrantedBy: application.UpdatedBy, }) // the extra members for extraMember, roleOfMember := range extraMembers { if extraMember.ID == application.CreatedBy { continue } members = append(members, &membermodels.Member{ ResourceType: membermodels.TypeApplication, ResourceID: application.ID, Role: roleOfMember, MemberType: membermodels.MemberUser, MemberNameID: extraMember.ID, GrantedBy: application.CreatedBy, }) } result := tx.Create(members) if result.Error != nil { return herrors.NewErrInsertFailed(herrors.ApplicationInDB, result.Error.Error()) } if result.RowsAffected == 0 { return herrors.NewErrInsertFailed(herrors.ApplicationInDB, "create member error") } return nil }) return application, err} Method `dao.UpdateByID` has 6 return statements (exceeds 4 allowed).func (d *dao) UpdateByID(ctx context.Context, id uint, application *models.Application) (*models.Application, error) { var applicationInDB models.Application if err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 1. get application in db first result := tx.Raw(common.ApplicationQueryByID, id).Scan(&applicationInDB) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return herrors.NewErrUpdateFailed(herrors.ApplicationInDB, result.Error.Error()) } if result.RowsAffected == 0 { return herrors.NewErrNotFound(herrors.ApplicationInDB, "rows affected = 0") } // 2. update value applicationInDB.Description = application.Description applicationInDB.Priority = application.Priority applicationInDB.GitURL = application.GitURL applicationInDB.GitSubfolder = application.GitSubfolder applicationInDB.GitRefType = application.GitRefType applicationInDB.GitRef = application.GitRef applicationInDB.Image = application.Image applicationInDB.Template = application.Template applicationInDB.TemplateRelease = application.TemplateRelease // 3. save application after updated tx.Save(&applicationInDB) return nil }); err != nil { return nil, err } return &applicationInDB, nil} Method `dao.DeleteByID` has 5 return statements (exceeds 4 allowed).func (d *dao) DeleteByID(ctx context.Context, id uint) error { currentUser, err := corecommon.UserFromContext(ctx) if err != nil { return err } result := d.db.WithContext(ctx).Exec(common.ApplicationDeleteByID, time.Now().Unix(), currentUser.GetID(), id) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return herrors.NewErrNotFound(herrors.ApplicationInDB, result.Error.Error()) } return herrors.NewErrDeleteFailed(herrors.ApplicationInDB, result.Error.Error()) } if result.RowsAffected == 0 { return herrors.NewErrNotFound(herrors.ApplicationInDB, "application not found") } return nil} Method `dao.TransferByID` has 7 return statements (exceeds 4 allowed).func (d *dao) TransferByID(ctx context.Context, id uint, groupID uint) error { currentUser, err := corecommon.UserFromContext(ctx) if err != nil { return err } err = d.db.Transaction(func(tx *gorm.DB) error { var group groupmodels.Group result := tx.Raw(common.GroupQueryByID, groupID).Scan(&group) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return herrors.NewErrNotFound(herrors.GroupInDB, "group not found") } result = tx.Exec(common.ApplicationTransferByID, groupID, currentUser.GetID(), id) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return herrors.NewErrNotFound(herrors.ApplicationInDB, "application not found") } return nil }) return err} Method `dao.List` has 63 lines of code (exceeds 50 allowed). Consider refactoring.
Method `dao.List` has a Cognitive Complexity of 25 (exceeds 20 allowed). Consider refactoring.func (d *dao) List(ctx context.Context, groupIDs []uint, query *q.Query) (int, []*models.Application, error) { var ( applications []*models.Application total int64 finalStatement *gorm.DB ) // basic filter genSQL := func() *gorm.DB { withDeleted := false statement := d.db.WithContext(ctx).Table("tb_application as a").Select("a.*") for k, v := range query.Keywords { switch k { case corecommon.ApplicationQueryName: statement = statement.Where("a.name like ?", fmt.Sprintf("%%%v%%", v)) case corecommon.ApplicationQueryByTemplate: statement = statement.Where("a.template = ?", v) case corecommon.ApplicationQueryByRelease: statement = statement.Where("a.template_release = ?", v) case corecommon.ApplicationQueryID: if reflect.TypeOf(v).Kind() == reflect.Slice { statement = statement.Where("a.id in ?", v) } else { statement = statement.Where("a.id = ?", v) } case corecommon.ApplicationQueryWithDeleted: withDeleted = true } } if !withDeleted { statement = statement.Where("a.deleted_ts = 0") } return statement } if query != nil { if userID, ok := query.Keywords[corecommon.ApplicationQueryByUser]; ok { // query of user's directly authorized applications statementUser := genSQL(). Joins("join tb_member as m on m.resource_id = a.id"). Where("m.resource_type = ?", corecommon.ResourceApplication). Where("m.member_type = '0'"). Where("m.membername_id = ?", userID). Where("m.deleted_ts = 0") if len(groupIDs) != 0 { // union query of authorized applications inherited from user's groups statementGroup := genSQL().Where("group_id in ?", groupIDs) finalStatement = d.db.Raw("? union ?", statementGroup, statementUser) } else { // user does not belong to any group finalStatement = statementUser } } else { finalStatement = genSQL() if len(groupIDs) != 0 { // query of applications filtered by groups finalStatement = finalStatement.Where("group_id in ?", groupIDs) } } } res := d.db.Raw("select count(distinct id) from (?) as apps", finalStatement).Scan(&total) if res.Error != nil { return 0, nil, herrors.NewErrGetFailed(herrors.ApplicationInDB, res.Error.Error()) } finalStatement = d.db.Raw("select distinct * from (?) as apps order by updated_at desc limit ? offset ?", finalStatement, query.Limit(), query.Offset()) res = finalStatement.Scan(&applications) if res.Error != nil { return 0, nil, herrors.NewErrGetFailed(herrors.ApplicationInDB, res.Error.Error()) } return int(total), applications, nil}