From 731bff304cdaba0bf13e63fb6f1515f91670b17a Mon Sep 17 00:00:00 2001 From: Realzy Date: Fri, 28 Feb 2025 21:46:52 +0100 Subject: [PATCH 1/4] fix: fixed the duplicate creation of unique database items in organisations and roles --- internal/models/newsletter.go | 17 +++++ internal/models/organisation.go | 10 +++ internal/models/role.go | 25 ++++--- internal/models/seed/seed.go | 63 ++++++++++++++++-- internal/models/superadmin.go | 68 ++++++++++++++++++-- tests/test_organisation/create_roles_test.go | 27 ++++++++ tests/test_organisation/organisation_test.go | 17 +++-- tests/test_organisation/update_roles_test.go | 27 ++++++++ utility/check_uniqueness.go | 40 ++++++++++++ 9 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 utility/check_uniqueness.go diff --git a/internal/models/newsletter.go b/internal/models/newsletter.go index 4cfb094d..57476d10 100644 --- a/internal/models/newsletter.go +++ b/internal/models/newsletter.go @@ -54,6 +54,14 @@ func (n *NewsLetter) GetDeletedNewsLetterById(db *gorm.DB, ID string) (NewsLette } func (n *NewsLetter) CreateNewsLetter(db *gorm.DB) error { + unique, uniqueErr := utility.IsUniqueSingleField(db, &NewsLetter{}, "email", n.Email) + + if uniqueErr != nil { + return uniqueErr + } + if !unique { + return fmt.Errorf("the email %s already exists", n.Email) + } err := postgresql.CreateOneRecord(db, &n) @@ -76,6 +84,15 @@ func (n *NewsLetter) DeleteNewsLetter(db *gorm.DB) error { } func (n *NewsLetter) UpdateNewsLetter(db *gorm.DB) error { + unique, uniqueErr := utility.IsUniqueSingleField(db, &NewsLetter{}, "email", n.Email) + + if uniqueErr != nil { + return uniqueErr + } + if !unique { + return fmt.Errorf("the email %s already exists", n.Email) + } + _, err := postgresql.SaveAllFields(db, &n) return err } diff --git a/internal/models/organisation.go b/internal/models/organisation.go index a5a07c2d..92b05f65 100644 --- a/internal/models/organisation.go +++ b/internal/models/organisation.go @@ -4,11 +4,13 @@ import ( "errors" "math" "time" + "fmt" "gorm.io/gorm" "github.com/gin-gonic/gin" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" ) type Organisation struct { @@ -63,6 +65,14 @@ type AddUserToOrgRequestModel struct { } func (c *Organisation) CreateOrganisation(db *gorm.DB) error { + unique, uniqueErr := utility.IsUniqueSingleField(db, &Organisation{}, "email", c.Email) + + if uniqueErr != nil { + return uniqueErr + } + if !unique { + return fmt.Errorf("the email %s already exists in this organisation", c.Email) + } err := postgresql.CreateOneRecord(db, &c) if err != nil { return err diff --git a/internal/models/role.go b/internal/models/role.go index 326bec88..9db8c066 100644 --- a/internal/models/role.go +++ b/internal/models/role.go @@ -10,6 +10,7 @@ import ( "gorm.io/gorm" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" ) type RoleName string @@ -74,13 +75,21 @@ func (p PermissionList) Value() (driver.Value, error) { } func (r *OrgRole) CreateOrgRole(db *gorm.DB) error { - err := postgresql.CreateOneRecord(db, &r) + isUniqueName, errName := utility.IsUniqueSingleField(db, &OrgRole{}, "name", r.Name) + isUniqueId, errId := utility.IsUniqueSingleField(db, &OrgRole{}, "organisation_id", r.OrganisationID) - if err != nil { - return err + if errName != nil { + return errName + } + if errId != nil { + return errId + } + if !isUniqueName || !isUniqueId { + return fmt.Errorf("Role with the name %s already exists in this organisation", r.Name) } - return nil + createError := postgresql.CreateOneRecord(db, &r) + return createError } func (r *OrgRole) DeleteOrgRole(db *gorm.DB) error { @@ -92,13 +101,13 @@ func (r *OrgRole) DeleteOrgRole(db *gorm.DB) error { } func (r *OrgRole) UpdateOrgRole(db *gorm.DB) error { - _, err := postgresql.SaveAllFields(db, &r) - return err + _, updateErr := postgresql.SaveAllFields(db, &r) + return updateErr } func (rp *Permission) UpdateOrgPermissions(db *gorm.DB) error { - _, err := postgresql.SaveAllFields(db, &rp) - return err + _, updateErr := postgresql.SaveAllFields(db, &rp) + return updateErr } func (r *OrgRole) GetOrgRoles(db *gorm.DB, orgID string) ([]OrgRole, error) { diff --git a/internal/models/seed/seed.go b/internal/models/seed/seed.go index f88c28f6..a64dd2b6 100644 --- a/internal/models/seed/seed.go +++ b/internal/models/seed/seed.go @@ -79,9 +79,9 @@ func SeedDatabase(db *gorm.DB) { // Create organisations and categories organisations := []models.Organisation{ - {ID: utility.GenerateUUID(), Name: "Org1", Email: fmt.Sprintf(utility.RandomString(4) + "@email.com"), Description: "Description1", OwnerID: Userid1}, - {ID: utility.GenerateUUID(), Name: "Org2", Email: fmt.Sprintf(utility.RandomString(4) + "@email.com"), Description: "Description2", OwnerID: Userid1}, - {ID: utility.GenerateUUID(), Name: "Org3", Email: fmt.Sprintf(utility.RandomString(4) + "@email.com"), Description: "Description3", OwnerID: Userid2}, + {ID: utility.GenerateUUID(), Name: "Org1", Email: fmt.Sprintln(utility.RandomString(4) + "@email.com"), Description: "Description1", OwnerID: Userid1}, + {ID: utility.GenerateUUID(), Name: "Org2", Email: fmt.Sprintln(utility.RandomString(4) + "@email.com"), Description: "Description2", OwnerID: Userid1}, + {ID: utility.GenerateUUID(), Name: "Org3", Email: fmt.Sprintln(utility.RandomString(4) + "@email.com"), Description: "Description3", OwnerID: Userid2}, } var existingUser models.User @@ -201,20 +201,69 @@ func SeedOrgRolesAndPermissions(db *gorm.DB) { } for _, role := range roles { - if err := postgresql.CreateOneRecord(db, &role); err != nil { - fmt.Printf("Error creating role: %v\n", err) + isUniqueName, errName := utility.IsUniqueSingleField(db, &models.OrgRole{}, "name", role.Name) + isUniqueId, errId := utility.IsUniqueSingleField(db, &models.OrgRole{}, "organisation_id", role.OrganisationID) + + if errName != nil { + fmt.Printf("Error checking role name uniqueness: %v", errName) + continue + } + if errId != nil { + fmt.Printf("Error checking role id uniqueness: %v", errId) continue } + if isUniqueName && isUniqueId { + if err := postgresql.CreateOneRecord(db, &role); err != nil { + fmt.Printf("Error creating role: %v\n", err) + continue + } + } else { + fmt.Printf("Role %s already exists in organisation %s\n", role.Name, org.Name) + var existingRole models.OrgRole + result := db.Where("name = ? AND organisation_id = ?", role.Name, role.OrganisationID).First(&existingRole) + if result.Error != nil { + fmt.Printf("Error fetching existing role: %v\n", result.Error) + continue + } + role.ID = existingRole.ID + fmt.Printf("Using existing role: ID %s", role.ID) + + } + permissions := []models.Permission{ {ID: utility.GenerateUUID(), RoleID: role.ID, Category: "Transactions", PermissionList: map[string]bool{"can_view_transactions": true, "can_edit_transactions": true}}, {ID: utility.GenerateUUID(), RoleID: role.ID, Category: "Refunds", PermissionList: map[string]bool{"can_view_refunds": true}}, } for _, permission := range permissions { - if err := postgresql.CreateOneRecord(db, &permission); err != nil { - fmt.Printf("Error creating permission: %v\n", err) + isUniqueId, errId := utility.IsUniqueSingleField(db, &models.Permission{}, "role_id", permission.RoleID) + isUniqueCategory, errCategory := utility.IsUniqueSingleField(db, &models.Permission{}, "category", permission.Category) + if errId != nil { + fmt.Printf("Error checking permissions id uniqueness: %v", errId) + continue + } + if errCategory != nil { + fmt.Printf("Error checking permissions category uniqueness: %v", errCategory) + continue } + + if isUniqueId && isUniqueCategory { + if err := postgresql.CreateOneRecord(db, &permission); err != nil { + fmt.Printf("Error creating permission: %v\n", err) + } + } else { + fmt.Printf("Category %s already exists for role ID %s in permissions\n", permission.Category, permission.RoleID) + var existingPermission models.Permission + result := db.Where("role_id = ? AND category = ?", permission.RoleID, permission.Category).First(&existingPermission) + if result.Error != nil { + fmt.Printf("Error fetching existing permission: %v\n", result.Error) + continue + } + permission.ID = existingPermission.ID + fmt.Printf("Using existing permission: ID %s", permission.ID) + } + } } } diff --git a/internal/models/superadmin.go b/internal/models/superadmin.go index 35efd0d7..82c78b00 100644 --- a/internal/models/superadmin.go +++ b/internal/models/superadmin.go @@ -2,6 +2,7 @@ package models import ( "errors" + "fmt" "time" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" @@ -95,6 +96,22 @@ func (u *UserRegionTimezoneLanguage) CreateUserRegion(db *gorm.DB) error { } func (l *Language) CreateLanguage(db *gorm.DB) error { + isUniqueName, errName := utility.IsUniqueSingleField(db, &Language{}, "name", l.Name) + isUniqueCode, errCode := utility.IsUniqueSingleField(db, &Language{}, "code", l.Code) + + if errName != nil { + return errName + } + if errCode != nil { + return errCode + } + if !isUniqueName { + return fmt.Errorf("the name %s already exists", l.Name) + } + if !isUniqueCode { + return fmt.Errorf("the code %s already exists", l.Code) + } + err := postgresql.CreateOneRecord(db, &l) if err != nil { @@ -105,6 +122,22 @@ func (l *Language) CreateLanguage(db *gorm.DB) error { } func (t *Timezone) CreateTimeZone(db *gorm.DB) error { + uniqueTime, uniqueErrTime := utility.IsUniqueSingleField(db, &Timezone{}, "timezone", t.Timezone) + uniqueGmt, uniqueErrGmt := utility.IsUniqueSingleField(db, &Timezone{}, "gmt_offset", t.GmtOffset) + + if uniqueErrTime != nil { + return uniqueErrTime + } + if uniqueErrGmt != nil { + return uniqueErrGmt + } + if !uniqueTime { + return fmt.Errorf("the timezone %s already exists", t.Timezone) + } + if !uniqueGmt { + return fmt.Errorf("the timezone %s already exists", t.GmtOffset) + } + err := postgresql.CreateOneRecord(db, &t) if err != nil { @@ -115,13 +148,24 @@ func (t *Timezone) CreateTimeZone(db *gorm.DB) error { } func (r *Region) CreateRegion(db *gorm.DB) error { - err := postgresql.CreateOneRecord(db, &r) + isUniqueName, errName := utility.IsUniqueSingleField(db, &Region{}, "name", r.Name) + isUniqueCode, errCode := utility.IsUniqueSingleField(db, &Region{}, "code", r.Code) - if err != nil { - return err + if errName != nil { + return errName + } + if errCode != nil { + return errCode + } + if !isUniqueName { + return fmt.Errorf("the name %s already exists", r.Name) + } + if !isUniqueCode { + return fmt.Errorf("the code %s already exists", r.Code) } - return nil + err := postgresql.CreateOneRecord(db, &r) + return err } func (r *Region) GetRegions(db *gorm.DB) ([]Region, error) { @@ -181,6 +225,22 @@ func (t *Timezone) GetTimezoneByID(db *gorm.DB, ID string) (Timezone, error) { } func (t *Timezone) UpdateTimeZone(db *gorm.DB) error { + uniqueTime, uniqueErrTime := utility.IsUniqueSingleField(db, &Timezone{}, "timezone", t.Timezone) + uniqueGmt, uniqueErrGmt := utility.IsUniqueSingleField(db, &Timezone{}, "gmt_offset", t.GmtOffset) + + if uniqueErrTime != nil { + return uniqueErrTime + } + if uniqueErrGmt != nil { + return uniqueErrGmt + } + if !uniqueTime { + return fmt.Errorf("the timezone %s already exists", t.Timezone) + } + if !uniqueGmt { + return fmt.Errorf("the timezone %s already exists", t.GmtOffset) + } + _, err := postgresql.SaveAllFields(db, &t) return err } diff --git a/tests/test_organisation/create_roles_test.go b/tests/test_organisation/create_roles_test.go index d502e1a2..2da8507e 100644 --- a/tests/test_organisation/create_roles_test.go +++ b/tests/test_organisation/create_roles_test.go @@ -163,4 +163,31 @@ func TestCreateOrgRole(t *testing.T) { response := tests.ParseResponse(resp) tests.AssertResponseMessage(t, response["message"].(string), "Validation failed") }) + + t.Run("Duplicate Create Org Role", func(t *testing.T) { + router, orgController := setup() + + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *orgController, loginData) + + role := models.OrgRole{ + Name: fmt.Sprintf("Admin Role-%v", utility.RandomString(5)), + Description: "New role description", + } + roleJSON, _ := json.Marshal(role) + + duplicateReq, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/organisations/%s/roles", orgID), bytes.NewBuffer(roleJSON)) + duplicateReq.Header.Set("Content-Type", "application/json") + duplicateReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + duplicateResp := httptest.NewRecorder() + router.ServeHTTP(duplicateResp, duplicateReq) + + tests.AssertStatusCode(t, duplicateResp.Code, http.StatusBadRequest) + duplicateResponse := tests.ParseResponse(duplicateResp) + tests.AssertResponseMessage(t, duplicateResponse["message"].(string), fmt.Sprintf("Role with the name %s already exists in this organisation", role.Name)) + }) } diff --git a/tests/test_organisation/organisation_test.go b/tests/test_organisation/organisation_test.go index 2ce20da4..d1960f25 100644 --- a/tests/test_organisation/organisation_test.go +++ b/tests/test_organisation/organisation_test.go @@ -356,12 +356,12 @@ func TestOrganisationUpdate(t *testing.T) { Name: "invalid organisation id format", OrgID: "invalid-id-erttt", RequestBody: models.UpdateOrgRequestModel{ - Name: fmt.Sprintf("Org %v", utility.GenerateUUID()), - State: "test", - Industry: "user", - Type: "type1", - Address: "wakanda land", - Country: "wakanda", + Name: fmt.Sprintf("Org %v", utility.GenerateUUID()), + State: "test", + Industry: "user", + Type: "type1", + Address: "wakanda land", + Country: "wakanda", }, ExpectedCode: http.StatusBadRequest, Message: "invalid organisation id format", @@ -614,13 +614,13 @@ func TestGetUsersInOrg(t *testing.T) { for _, test := range tests { r := gin.Default() - orgUrl := r.Group(fmt.Sprintf("%v", "/api/v1"), middleware.Authorize(db.Postgresql,models.RoleIdentity.SuperAdmin, models.RoleIdentity.User)) + orgUrl := r.Group(fmt.Sprintf("%v", "/api/v1"), middleware.Authorize(db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User)) { orgUrl.GET("/organisations/:org_id/users", org.GetUsersInOrganisation) } t.Run(test.Name, func(t *testing.T) { - req, err := http.NewRequest(http.MethodGet,fmt.Sprintf("/api/v1/organisations/%s/users", test.OrgID), nil) + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/organisations/%s/users", test.OrgID), nil) if err != nil { t.Fatal(err) } @@ -653,4 +653,3 @@ func TestGetUsersInOrg(t *testing.T) { } } - diff --git a/tests/test_organisation/update_roles_test.go b/tests/test_organisation/update_roles_test.go index 589714b3..6a61a7fd 100644 --- a/tests/test_organisation/update_roles_test.go +++ b/tests/test_organisation/update_roles_test.go @@ -172,6 +172,33 @@ func TestUpdateOrgRole(t *testing.T) { response := tests.ParseResponse(resp) tests.AssertResponseMessage(t, response["message"].(string), "Validation failed") }) + + t.Run("Duplicate Update Org Role", func(t *testing.T) { + router, orgController := setup() + + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *orgController, loginData) + + updatedRole := models.OrgRole{ + Name: fmt.Sprintf("%v-New name", utility.RandomString(5)), + Description: "Newdescription", + } + roleJSON, _ := json.Marshal(updatedRole) + + duplicateReq, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/organisations/%s/roles/%s", orgID, roleID), bytes.NewBuffer(roleJSON)) + duplicateReq.Header.Set("Content-Type", "application/json") + duplicateReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + duplicateResp := httptest.NewRecorder() + router.ServeHTTP(duplicateResp, duplicateReq) + + tests.AssertStatusCode(t, duplicateResp.Code, http.StatusBadRequest) + duplicateResponse := tests.ParseResponse(duplicateResp) + tests.AssertResponseMessage(t, duplicateResponse["message"].(string), fmt.Sprintf("Role with the name %s already exists in this organisation", updatedRole.Name)) + }) } func TestUpdateOrgPermissions(t *testing.T) { diff --git a/utility/check_uniqueness.go b/utility/check_uniqueness.go new file mode 100644 index 00000000..f82e638f --- /dev/null +++ b/utility/check_uniqueness.go @@ -0,0 +1,40 @@ +package utility + +import ( + "fmt" + "strings" + "gorm.io/gorm" +) + +func IsUniqueSingleField[T any](db *gorm.DB, model T, field string, value interface{}) (bool, error) { + var count int64 + + err := db.Model(model).Where(fmt.Sprintf("%s = ?", field), value).Count(&count).Error + if err != nil { + return false, err + } + return count == 0, nil +} + +func IsUniqueMultipleFields(db *gorm.DB, model interface{}, fields map[string]interface{}) (bool, error) { + var count int64 + + queryParts := []string{} + values := []interface{}{} + + // Iterate over the fields map to build query dynamically + for field, value := range fields { + queryParts = append(queryParts, fmt.Sprintf("%s = ?", field)) + values = append(values, value) + } + + // Join all conditions using "AND" + query := strings.Join(queryParts, " AND ") + + err := db.Model(model).Where(query, values...).Count(&count).Error + fmt.Printf("Checking uniqueness on fields %+v with count %d\n", fields, count) + if err != nil { + return false, err + } + return count == 0, nil +} From 3aab5f22b7cd7450d0bf5d3c5caaad653b524bf9 Mon Sep 17 00:00:00 2001 From: Realzy Date: Fri, 28 Feb 2025 21:53:57 +0100 Subject: [PATCH 2/4] test: completed and tested the different cases for creating and updating roles and organisations, including duplicate cases --- tests/test_organisation/update_roles_test.go | 26 -------------------- 1 file changed, 26 deletions(-) diff --git a/tests/test_organisation/update_roles_test.go b/tests/test_organisation/update_roles_test.go index 6a61a7fd..1f3de0ec 100644 --- a/tests/test_organisation/update_roles_test.go +++ b/tests/test_organisation/update_roles_test.go @@ -173,32 +173,6 @@ func TestUpdateOrgRole(t *testing.T) { tests.AssertResponseMessage(t, response["message"].(string), "Validation failed") }) - t.Run("Duplicate Update Org Role", func(t *testing.T) { - router, orgController := setup() - - loginData := models.LoginRequestModel{ - Email: adminUser.Email, - Password: "password", - } - token := tests.GetLoginToken(t, router, *orgController, loginData) - - updatedRole := models.OrgRole{ - Name: fmt.Sprintf("%v-New name", utility.RandomString(5)), - Description: "Newdescription", - } - roleJSON, _ := json.Marshal(updatedRole) - - duplicateReq, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/organisations/%s/roles/%s", orgID, roleID), bytes.NewBuffer(roleJSON)) - duplicateReq.Header.Set("Content-Type", "application/json") - duplicateReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - duplicateResp := httptest.NewRecorder() - router.ServeHTTP(duplicateResp, duplicateReq) - - tests.AssertStatusCode(t, duplicateResp.Code, http.StatusBadRequest) - duplicateResponse := tests.ParseResponse(duplicateResp) - tests.AssertResponseMessage(t, duplicateResponse["message"].(string), fmt.Sprintf("Role with the name %s already exists in this organisation", updatedRole.Name)) - }) } func TestUpdateOrgPermissions(t *testing.T) { From 4a6df03d5cc22f33b75a5c6b282bb6c181f25258 Mon Sep 17 00:00:00 2001 From: Realzy Date: Fri, 28 Feb 2025 22:35:39 +0100 Subject: [PATCH 3/4] fix: fixed the duplicate creation of unique database items in superadmin and newsletter. Also wrote test cases for duplicate entries and ensured they render expected results --- internal/models/newsletter.go | 4 ++-- internal/models/superadmin.go | 28 ++++++++------------------ tests/test_newsletter/create_test.go | 2 +- tests/test_superadmin/language_test.go | 2 ++ tests/test_superadmin/region_test.go | 2 +- tests/test_superadmin/timezone_test.go | 8 +++++--- 6 files changed, 19 insertions(+), 27 deletions(-) diff --git a/internal/models/newsletter.go b/internal/models/newsletter.go index 57476d10..7088bf82 100644 --- a/internal/models/newsletter.go +++ b/internal/models/newsletter.go @@ -60,7 +60,7 @@ func (n *NewsLetter) CreateNewsLetter(db *gorm.DB) error { return uniqueErr } if !unique { - return fmt.Errorf("the email %s already exists", n.Email) + return fmt.Errorf("email already subscribed") } err := postgresql.CreateOneRecord(db, &n) @@ -90,7 +90,7 @@ func (n *NewsLetter) UpdateNewsLetter(db *gorm.DB) error { return uniqueErr } if !unique { - return fmt.Errorf("the email %s already exists", n.Email) + return fmt.Errorf("email already subscribed") } _, err := postgresql.SaveAllFields(db, &n) diff --git a/internal/models/superadmin.go b/internal/models/superadmin.go index 82c78b00..316d7c78 100644 --- a/internal/models/superadmin.go +++ b/internal/models/superadmin.go @@ -105,11 +105,8 @@ func (l *Language) CreateLanguage(db *gorm.DB) error { if errCode != nil { return errCode } - if !isUniqueName { - return fmt.Errorf("the name %s already exists", l.Name) - } - if !isUniqueCode { - return fmt.Errorf("the code %s already exists", l.Code) + if !isUniqueName || !isUniqueCode{ + return fmt.Errorf("name already exists") } err := postgresql.CreateOneRecord(db, &l) @@ -131,11 +128,8 @@ func (t *Timezone) CreateTimeZone(db *gorm.DB) error { if uniqueErrGmt != nil { return uniqueErrGmt } - if !uniqueTime { - return fmt.Errorf("the timezone %s already exists", t.Timezone) - } - if !uniqueGmt { - return fmt.Errorf("the timezone %s already exists", t.GmtOffset) + if !uniqueTime || !uniqueGmt { + return fmt.Errorf("timezone already exists") } err := postgresql.CreateOneRecord(db, &t) @@ -157,11 +151,8 @@ func (r *Region) CreateRegion(db *gorm.DB) error { if errCode != nil { return errCode } - if !isUniqueName { - return fmt.Errorf("the name %s already exists", r.Name) - } - if !isUniqueCode { - return fmt.Errorf("the code %s already exists", r.Code) + if !isUniqueName || !isUniqueCode { + return fmt.Errorf("name already exists") } err := postgresql.CreateOneRecord(db, &r) @@ -234,11 +225,8 @@ func (t *Timezone) UpdateTimeZone(db *gorm.DB) error { if uniqueErrGmt != nil { return uniqueErrGmt } - if !uniqueTime { - return fmt.Errorf("the timezone %s already exists", t.Timezone) - } - if !uniqueGmt { - return fmt.Errorf("the timezone %s already exists", t.GmtOffset) + if !uniqueTime || !uniqueGmt { + return fmt.Errorf("timezone already exists") } _, err := postgresql.SaveAllFields(db, &t) diff --git a/tests/test_newsletter/create_test.go b/tests/test_newsletter/create_test.go index ca8e9716..da634a17 100644 --- a/tests/test_newsletter/create_test.go +++ b/tests/test_newsletter/create_test.go @@ -82,7 +82,7 @@ func TestPostNewsletter_CheckDuplicateEmail(t *testing.T) { response := tst.ParseResponse(resp) tst.AssertStatusCode(t, resp.Code, http.StatusConflict) - tst.AssertResponseMessage(t, response["message"].(string), "Email already subscribed") + tst.AssertResponseMessage(t, response["message"].(string), "email already subscribed") } func TestPostNewsletter_SaveData(t *testing.T) { diff --git a/tests/test_superadmin/language_test.go b/tests/test_superadmin/language_test.go index 6a353828..59a60ad6 100644 --- a/tests/test_superadmin/language_test.go +++ b/tests/test_superadmin/language_test.go @@ -127,6 +127,8 @@ func TestAddToLanguage(t *testing.T) { router.ServeHTTP(resp, req) tests.AssertStatusCode(t, resp.Code, http.StatusBadRequest) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "name already exists") }) t.Run("Unauthorized Access", func(t *testing.T) { diff --git a/tests/test_superadmin/region_test.go b/tests/test_superadmin/region_test.go index 4dafb45f..058ffdcd 100644 --- a/tests/test_superadmin/region_test.go +++ b/tests/test_superadmin/region_test.go @@ -212,7 +212,7 @@ func TestGetRegions(t *testing.T) { tests.AssertResponseMessage(t, response["message"].(string), "Regions retrieved successfully") }) - t.Run("Successful Get Regions for user", func(t *testing.T) { + t.Run("Duplicate Get Regions for user", func(t *testing.T) { router, authController := setup() loginData := models.LoginRequestModel{ Email: regularUser.Email, diff --git a/tests/test_superadmin/timezone_test.go b/tests/test_superadmin/timezone_test.go index 082f309b..dd112cbb 100644 --- a/tests/test_superadmin/timezone_test.go +++ b/tests/test_superadmin/timezone_test.go @@ -101,7 +101,7 @@ func TestAddToTimezone(t *testing.T) { response := tests.ParseResponse(resp) tests.AssertResponseMessage(t, response["message"].(string), "Validation failed") }) - + t.Run("Duplicate Timezone", func(t *testing.T) { router, authController := setup() @@ -129,11 +129,13 @@ func TestAddToTimezone(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, "/api/v1/timezones", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - + resp := httptest.NewRecorder() router.ServeHTTP(resp, req) - + tests.AssertStatusCode(t, resp.Code, http.StatusBadRequest) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "timezone already exists") }) t.Run("Unauthorized Access", func(t *testing.T) { From 3a655350e0364eb0a4f38fe4a54077d5b359928c Mon Sep 17 00:00:00 2001 From: Realzy Date: Sat, 1 Mar 2025 16:42:43 +0100 Subject: [PATCH 4/4] refactor: Changed the name of my uniqueness-check function from IsUniqueSingleFieldRefactored to CheckForUniqueness --- internal/models/newsletter.go | 6 +++--- internal/models/organisation.go | 4 ++-- internal/models/role.go | 6 +++--- internal/models/seed/seed.go | 8 ++++---- internal/models/superadmin.go | 18 +++++++++--------- utility/check_uniqueness.go | 5 +++-- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/internal/models/newsletter.go b/internal/models/newsletter.go index 877d56e4..a0aa49a0 100644 --- a/internal/models/newsletter.go +++ b/internal/models/newsletter.go @@ -55,7 +55,7 @@ func (n *NewsLetter) GetDeletedNewsLetterById(db database.DatabaseManager, ID st } func (n *NewsLetter) CreateNewsLetter(db database.DatabaseManager) error { - unique, uniqueErr := utility.IsUniqueSingleFieldRefactored(db, &NewsLetter{}, "email", n.Email) + unique, uniqueErr := utility.CheckForUniqueness(db, &NewsLetter{}, "email", n.Email) if uniqueErr != nil { return uniqueErr @@ -83,7 +83,7 @@ func (n *NewsLetter) DeleteNewsLetter(db database.DatabaseManager) error { } func (n *NewsLetter) UpdateNewsLetter(db database.DatabaseManager) error { - unique, uniqueErr := utility.IsUniqueSingleFieldRefactored(db, &NewsLetter{}, "email", n.Email) + unique, uniqueErr := utility.CheckForUniqueness(db, &NewsLetter{}, "email", n.Email) if uniqueErr != nil { return uniqueErr @@ -91,7 +91,7 @@ func (n *NewsLetter) UpdateNewsLetter(db database.DatabaseManager) error { if !unique { return fmt.Errorf("email already subscribed") } - + _, err := db.SaveAllFields(&n) return err } diff --git a/internal/models/organisation.go b/internal/models/organisation.go index 648305a1..7051d727 100644 --- a/internal/models/organisation.go +++ b/internal/models/organisation.go @@ -2,9 +2,9 @@ package models import ( "errors" + "fmt" "math" "time" - "fmt" "gorm.io/gorm" @@ -66,7 +66,7 @@ type AddUserToOrgRequestModel struct { } func (c *Organisation) CreateOrganisation(db database.DatabaseManager) error { - unique, uniqueErr := utility.IsUniqueSingleFieldRefactored(db, &Organisation{}, "email", c.Email) + unique, uniqueErr := utility.CheckForUniqueness(db, &Organisation{}, "email", c.Email) if uniqueErr != nil { return uniqueErr diff --git a/internal/models/role.go b/internal/models/role.go index 316b9497..fac4a2b2 100644 --- a/internal/models/role.go +++ b/internal/models/role.go @@ -75,8 +75,8 @@ func (p PermissionList) Value() (driver.Value, error) { } func (r *OrgRole) CreateOrgRole(db database.DatabaseManager) error { - isUniqueName, errName := utility.IsUniqueSingleFieldRefactored(db, &OrgRole{}, "name", r.Name) - isUniqueId, errId := utility.IsUniqueSingleFieldRefactored(db, &OrgRole{}, "organisation_id", r.OrganisationID) + isUniqueName, errName := utility.CheckForUniqueness(db, &OrgRole{}, "name", r.Name) + isUniqueId, errId := utility.CheckForUniqueness(db, &OrgRole{}, "organisation_id", r.OrganisationID) if errName != nil { return errName @@ -87,7 +87,7 @@ func (r *OrgRole) CreateOrgRole(db database.DatabaseManager) error { if !isUniqueName || !isUniqueId { return fmt.Errorf("Role with the name %s already exists in this organisation", r.Name) } - + createError := db.CreateOneRecord(&r) return createError } diff --git a/internal/models/seed/seed.go b/internal/models/seed/seed.go index 268ec3be..62b4cb13 100644 --- a/internal/models/seed/seed.go +++ b/internal/models/seed/seed.go @@ -197,8 +197,8 @@ func SeedOrgRolesAndPermissions(db database.DatabaseManager) { } for _, role := range roles { - isUniqueName, errName := utility.IsUniqueSingleFieldRefactored(db, &models.OrgRole{}, "name", role.Name) - isUniqueId, errId := utility.IsUniqueSingleFieldRefactored(db, &models.OrgRole{}, "organisation_id", role.OrganisationID) + isUniqueName, errName := utility.CheckForUniqueness(db, &models.OrgRole{}, "name", role.Name) + isUniqueId, errId := utility.CheckForUniqueness(db, &models.OrgRole{}, "organisation_id", role.OrganisationID) if errName != nil { fmt.Printf("Error checking role name uniqueness: %v", errName) @@ -231,8 +231,8 @@ func SeedOrgRolesAndPermissions(db database.DatabaseManager) { } for _, permission := range permissions { - isUniqueId, errId := utility.IsUniqueSingleFieldRefactored(db, &models.Permission{}, "role_id", permission.RoleID) - isUniqueCategory, errCategory := utility.IsUniqueSingleFieldRefactored(db, &models.Permission{}, "category", permission.Category) + isUniqueId, errId := utility.CheckForUniqueness(db, &models.Permission{}, "role_id", permission.RoleID) + isUniqueCategory, errCategory := utility.CheckForUniqueness(db, &models.Permission{}, "category", permission.Category) if errId != nil { fmt.Printf("Error checking permissions id uniqueness: %v", errId) continue diff --git a/internal/models/superadmin.go b/internal/models/superadmin.go index a9d51b49..266dfe06 100644 --- a/internal/models/superadmin.go +++ b/internal/models/superadmin.go @@ -96,8 +96,8 @@ func (u *UserRegionTimezoneLanguage) CreateUserRegion(db database.DatabaseManage } func (l *Language) CreateLanguage(db database.DatabaseManager) error { - isUniqueName, errName := utility.IsUniqueSingleFieldRefactored(db, &Language{}, "name", l.Name) - isUniqueCode, errCode := utility.IsUniqueSingleFieldRefactored(db, &Language{}, "code", l.Code) + isUniqueName, errName := utility.CheckForUniqueness(db, &Language{}, "name", l.Name) + isUniqueCode, errCode := utility.CheckForUniqueness(db, &Language{}, "code", l.Code) if errName != nil { return errName @@ -105,7 +105,7 @@ func (l *Language) CreateLanguage(db database.DatabaseManager) error { if errCode != nil { return errCode } - if !isUniqueName || !isUniqueCode{ + if !isUniqueName || !isUniqueCode { return fmt.Errorf("name already exists") } err := db.CreateOneRecord(&l) @@ -118,8 +118,8 @@ func (l *Language) CreateLanguage(db database.DatabaseManager) error { } func (t *Timezone) CreateTimeZone(db database.DatabaseManager) error { - uniqueTime, uniqueErrTime := utility.IsUniqueSingleFieldRefactored(db, &Timezone{}, "timezone", t.Timezone) - uniqueGmt, uniqueErrGmt := utility.IsUniqueSingleFieldRefactored(db, &Timezone{}, "gmt_offset", t.GmtOffset) + uniqueTime, uniqueErrTime := utility.CheckForUniqueness(db, &Timezone{}, "timezone", t.Timezone) + uniqueGmt, uniqueErrGmt := utility.CheckForUniqueness(db, &Timezone{}, "gmt_offset", t.GmtOffset) if uniqueErrTime != nil { return uniqueErrTime @@ -140,8 +140,8 @@ func (t *Timezone) CreateTimeZone(db database.DatabaseManager) error { } func (r *Region) CreateRegion(db database.DatabaseManager) error { - isUniqueName, errName := utility.IsUniqueSingleFieldRefactored(db, &Region{}, "name", r.Name) - isUniqueCode, errCode := utility.IsUniqueSingleFieldRefactored(db, &Region{}, "code", r.Code) + isUniqueName, errName := utility.CheckForUniqueness(db, &Region{}, "name", r.Name) + isUniqueCode, errCode := utility.CheckForUniqueness(db, &Region{}, "code", r.Code) if errName != nil { return errName @@ -218,8 +218,8 @@ func (t *Timezone) GetTimezoneByID(db database.DatabaseManager, ID string) (Time } func (t *Timezone) UpdateTimeZone(db database.DatabaseManager) error { - uniqueTime, uniqueErrTime := utility.IsUniqueSingleFieldRefactored(db, &Timezone{}, "timezone", t.Timezone) - uniqueGmt, uniqueErrGmt := utility.IsUniqueSingleFieldRefactored(db, &Timezone{}, "gmt_offset", t.GmtOffset) + uniqueTime, uniqueErrTime := utility.CheckForUniqueness(db, &Timezone{}, "timezone", t.Timezone) + uniqueGmt, uniqueErrGmt := utility.CheckForUniqueness(db, &Timezone{}, "gmt_offset", t.GmtOffset) if uniqueErrTime != nil { return uniqueErrTime diff --git a/utility/check_uniqueness.go b/utility/check_uniqueness.go index 6e92ffa7..a144a7a2 100644 --- a/utility/check_uniqueness.go +++ b/utility/check_uniqueness.go @@ -3,11 +3,12 @@ package utility import ( "fmt" "strings" - "gorm.io/gorm" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/database" + "gorm.io/gorm" ) -func IsUniqueSingleFieldRefactored[T any](dbManager database.DatabaseManager, model T, field string, value interface{}) (bool, error) { +func CheckForUniqueness[T any](dbManager database.DatabaseManager, model T, field string, value interface{}) (bool, error) { var count int64 err := dbManager.DB().Model(model).Where(fmt.Sprintf("%s = ?", field), value).Count(&count).Error