Skip to content

Commit d31b310

Browse files
author
Dev Agent
committed
fix: prioritize org namespace matches in search
1 parent faec695 commit d31b310

4 files changed

Lines changed: 179 additions & 13 deletions

File tree

builder/store/database/organization.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type Organization struct {
5858
OrgType string `bun:"" json:"org_type"`
5959
User *User `bun:"rel:belongs-to,join:user_id=id" json:"user"`
6060
NamespaceID int64 `bun:",notnull" json:"namespace_id"`
61-
Namespace *Namespace `bun:"rel:has-one,join:namespace_id=id" json:"namespace"`
61+
Namespace *Namespace `bun:"rel:has-one,join:path=path" json:"namespace"`
6262
VerifyStatus types.VerifyStatus `bun:",notnull,default:'none'" json:"verify_status"` // none, pending, approved, rejected
6363
UUID uuid.UUID `bun:"type:uuid,notnull,unique" json:"uuid"`
6464
times
@@ -78,6 +78,11 @@ func (s *orgStoreImpl) Create(ctx context.Context, org *Organization, namepace *
7878
if err != nil {
7979
return err
8080
}
81+
// update org's namespace id
82+
org.NamespaceID = namepace.ID
83+
if err = assertAffectedOneRow(tx.NewUpdate().Model(org).WherePK().Exec(ctx)); err != nil {
84+
return err
85+
}
8186
return nil
8287
})
8388
err = errorx.HandleDBError(err, nil)
@@ -88,6 +93,7 @@ func (s *orgStoreImpl) GetUserOwnOrgs(ctx context.Context, username string) (org
8893
query := s.db.Operator.Core.
8994
NewSelect().
9095
Model(&orgs).
96+
Relation("Namespace").
9197
Relation("User")
9298
if username != "" {
9399
query = query.
@@ -138,8 +144,8 @@ func (s *orgStoreImpl) FindByPath(ctx context.Context, path string) (org Organiz
138144
org.Nickname = path
139145
err = s.db.Operator.Core.
140146
NewSelect().
141-
Model(&org).
142-
Where("path =?", path).
147+
Model(&org).Relation("Namespace").
148+
Where("organization.path =?", path).
143149
Scan(ctx)
144150
return org, errorx.HandleDBError(err, nil)
145151
}
@@ -161,6 +167,7 @@ func (s *orgStoreImpl) GetUserBelongOrgs(ctx context.Context, userID int64) (org
161167
err = s.db.Operator.Core.
162168
NewSelect().
163169
Model(&orgs).
170+
Relation("Namespace").
164171
Join("join members on members.organization_id = organization.id").
165172
Where("members.user_id = ? and members.deleted_at is null", userID).
166173
Scan(ctx, &orgs)
@@ -170,9 +177,18 @@ func (s *orgStoreImpl) GetUserBelongOrgs(ctx context.Context, userID int64) (org
170177
func (s *orgStoreImpl) Search(ctx context.Context, search string, per int, page int, orgType, verifyStatus string) (orgs []Organization, total int, err error) {
171178
search = strings.ToLower(search)
172179
query := s.db.Operator.Core.NewSelect().
173-
Model(&orgs)
180+
Model(&orgs).Relation("Namespace")
174181
if search != "" {
175-
query.Where("LOWER(name) like ? OR LOWER(path) like ?", fmt.Sprintf("%%%s%%", search), fmt.Sprintf("%%%s%%", search))
182+
query.Where("LOWER(organization.name) like ? OR LOWER(organization.path) like ?", fmt.Sprintf("%%%s%%", search), fmt.Sprintf("%%%s%%", search))
183+
query.OrderExpr(`
184+
CASE
185+
WHEN LOWER(organization.path) = ? THEN 0
186+
WHEN LOWER(organization.path) LIKE ? THEN 1
187+
WHEN LOWER(organization.name) = ? THEN 2
188+
WHEN LOWER(organization.name) LIKE ? THEN 3
189+
ELSE 4
190+
END
191+
`, search, fmt.Sprintf("%s%%", search), search, fmt.Sprintf("%s%%", search))
176192
}
177193
if orgType != "" {
178194
query.Where("org_type = ?", orgType)
@@ -227,8 +243,8 @@ func (s *orgStoreImpl) GetSharedOrgIDs(ctx context.Context, userIDs []int64) ([]
227243
func (s *orgStoreImpl) FindByUUID(ctx context.Context, uuid string) (*Organization, error) {
228244
var org Organization
229245
err := s.db.Operator.Core.NewSelect().
230-
Model(&org).
231-
Where("uuid = ?", uuid).
246+
Model(&org).Relation("Namespace").
247+
Where("organization.uuid = ?", uuid).
232248
Scan(ctx)
233249
if err == nil {
234250
return &org, nil

builder/store/database/organization_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66
"errors"
7+
"slices"
78
"testing"
89
"time"
910

@@ -259,3 +260,85 @@ func TestOrganizationStore_FindByUUID(t *testing.T) {
259260
require.NotNil(t, err)
260261
require.Nil(t, org)
261262
}
263+
264+
func TestOrganizationStore_SearchOrder(t *testing.T) {
265+
db := tests.InitTestDB()
266+
defer db.Close()
267+
ctx := context.TODO()
268+
269+
store := database.NewOrgStoreWithDB(db)
270+
orgsToCreate := []database.Organization{
271+
{
272+
Name: "sss",
273+
Nickname: "zzz org",
274+
UUID: uuid.New(),
275+
},
276+
{
277+
Name: "sss-team",
278+
Nickname: "alpha org",
279+
UUID: uuid.New(),
280+
},
281+
{
282+
Name: "team-01",
283+
Nickname: "sss",
284+
UUID: uuid.New(),
285+
},
286+
{
287+
Name: "team-02",
288+
Nickname: "sss group",
289+
UUID: uuid.New(),
290+
},
291+
{
292+
Name: "team-03",
293+
Nickname: "group sss",
294+
UUID: uuid.New(),
295+
},
296+
}
297+
298+
for _, org := range orgsToCreate {
299+
err := store.Create(ctx, &org, &database.Namespace{Path: org.Name})
300+
require.Nil(t, err)
301+
}
302+
303+
orgs, total, err := store.Search(ctx, "sss", 10, 1, "", "")
304+
require.Nil(t, err)
305+
require.Equal(t, 5, total)
306+
307+
gotNames := make([]string, 0, len(orgs))
308+
for _, org := range orgs {
309+
gotNames = append(gotNames, org.Name)
310+
}
311+
312+
require.Equal(t, []string{"sss", "sss-team", "team-01", "team-02", "team-03"}, gotNames)
313+
}
314+
315+
func TestOrganizationStore_SearchOrderCaseInsensitive(t *testing.T) {
316+
db := tests.InitTestDB()
317+
defer db.Close()
318+
ctx := context.TODO()
319+
320+
store := database.NewOrgStoreWithDB(db)
321+
err := store.Create(ctx, &database.Organization{
322+
Name: "SSS-Exact",
323+
Nickname: "display",
324+
UUID: uuid.New(),
325+
}, &database.Namespace{Path: "SSS-Exact"})
326+
require.Nil(t, err)
327+
err = store.Create(ctx, &database.Organization{
328+
Name: "other",
329+
Nickname: "sss",
330+
UUID: uuid.New(),
331+
}, &database.Namespace{Path: "other"})
332+
require.Nil(t, err)
333+
334+
orgs, total, err := store.Search(ctx, "sss-exact", 10, 1, "", "")
335+
require.Nil(t, err)
336+
require.Equal(t, 1, total)
337+
require.Len(t, orgs, 1)
338+
require.Equal(t, "SSS-Exact", orgs[0].Name)
339+
340+
orgs, total, err = store.Search(ctx, "SSS", 10, 1, "", "")
341+
require.Nil(t, err)
342+
require.Equal(t, 2, total)
343+
require.True(t, slices.Equal([]string{"SSS-Exact", "other"}, []string{orgs[0].Name, orgs[1].Name}))
344+
}

user/component/organization.go

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type OrganizationComponent interface {
2121
Create(ctx context.Context, req *types.CreateOrgReq) (*types.Organization, error)
2222
Index(ctx context.Context, username, search string, per, page int, orgType, verifyStatus string) ([]types.Organization, int, error)
2323
Get(ctx context.Context, orgName string) (*types.Organization, error)
24+
GetByUUID(ctx context.Context, uuid string) (*types.Organization, error)
2425
Delete(ctx context.Context, req *types.DeleteOrgReq) error
2526
Update(ctx context.Context, req *types.EditOrgReq) (*database.Organization, error)
2627
}
@@ -98,15 +99,24 @@ func (c *organizationComponentImpl) Create(ctx context.Context, req *types.Creat
9899
dbOrg.Logo = req.Logo
99100
dbOrg.OrgType = req.OrgType
100101
dbOrg.Verified = req.Verified
102+
dbOrg.UUID = uuid.New()
103+
104+
exist, err := c.nsStore.ExistsByUUID(ctx, dbOrg.UUID.String())
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
// use the org uuid as the namespace uuid by default
110+
newNSUUID := dbOrg.UUID.String()
111+
if exist {
112+
// generate a new uuid if the uuid already exists
113+
newNSUUID = uuid.New().String()
114+
}
115+
101116
namespace := &database.Namespace{
102117
Path: dbOrg.Name,
103118
UserID: user.ID,
104-
}
105-
dbOrg.UUID = uuid.New()
106-
107-
chkUser, err := c.userStore.FindByUUID(ctx, dbOrg.UUID.String())
108-
if err == nil && chkUser != nil && chkUser.UUID == dbOrg.UUID.String() {
109-
return nil, errorx.UUIDConflict(dbOrg.UUID.String())
119+
UUID: newNSUUID,
110120
}
111121
err = c.orgStore.Create(ctx, dbOrg, namespace)
112122
if err != nil {
@@ -131,6 +141,11 @@ func (c *organizationComponentImpl) Create(ctx context.Context, req *types.Creat
131141
OrgType: dbOrg.OrgType,
132142
Verified: dbOrg.Verified,
133143
UUID: dbOrg.UUID,
144+
Namespace: &types.Namespace{
145+
Path: dbOrg.Name,
146+
Type: string(namespace.NamespaceType),
147+
UUID: namespace.UUID,
148+
},
134149
}
135150
return org, err
136151
}
@@ -169,6 +184,13 @@ func (c *organizationComponentImpl) Index(ctx context.Context, username, search
169184
VerifyStatus: string(dborg.VerifyStatus),
170185
UUID: dborg.UUID,
171186
}
187+
if dborg.Namespace != nil {
188+
org.Namespace = &types.Namespace{
189+
Path: dborg.Namespace.Path,
190+
Type: string(dborg.Namespace.NamespaceType),
191+
UUID: dborg.Namespace.UUID,
192+
}
193+
}
172194
orgs = append(orgs, org)
173195
}
174196
return orgs, total, nil
@@ -188,6 +210,37 @@ func (c *organizationComponentImpl) Get(ctx context.Context, orgName string) (*t
188210
Verified: dborg.Verified,
189211
UUID: dborg.UUID,
190212
}
213+
if dborg.Namespace != nil {
214+
org.Namespace = &types.Namespace{
215+
Path: dborg.Nickname,
216+
Type: string(dborg.Namespace.NamespaceType),
217+
UUID: dborg.Namespace.UUID,
218+
}
219+
}
220+
return org, nil
221+
}
222+
223+
func (c *organizationComponentImpl) GetByUUID(ctx context.Context, uuid string) (*types.Organization, error) {
224+
dborg, err := c.orgStore.FindByUUID(ctx, uuid)
225+
if err != nil {
226+
return nil, fmt.Errorf("failed to get organization by uuid, error: %w", err)
227+
}
228+
org := &types.Organization{
229+
Name: dborg.Name,
230+
Nickname: dborg.Nickname,
231+
Homepage: dborg.Homepage,
232+
Logo: dborg.Logo,
233+
OrgType: dborg.OrgType,
234+
Verified: dborg.Verified,
235+
UUID: dborg.UUID,
236+
}
237+
if dborg.Namespace != nil {
238+
org.Namespace = &types.Namespace{
239+
Path: dborg.Nickname,
240+
Type: string(dborg.Namespace.NamespaceType),
241+
UUID: dborg.Namespace.UUID,
242+
}
243+
}
191244
return org, nil
192245
}
193246

user/component/organization_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ func TestOrganizationComponent_Index(t *testing.T) {
7878
Logo: "org-logo.png",
7979
OrgType: "school",
8080
Verified: false,
81+
Namespace: &database.Namespace{
82+
Path: "org1",
83+
NamespaceType: database.OrgNamespace,
84+
},
8185
})
8286
dbOrgs = append(dbOrgs, database.Organization{
8387
ID: 2,
@@ -87,6 +91,10 @@ func TestOrganizationComponent_Index(t *testing.T) {
8791
Logo: "org-logo.png",
8892
OrgType: "school",
8993
Verified: false,
94+
Namespace: &database.Namespace{
95+
Path: "org2",
96+
NamespaceType: database.OrgNamespace,
97+
},
9098
})
9199
mockOrgStore := mockdb.NewMockOrgStore(t)
92100
mockOrgStore.EXPECT().GetUserOwnOrgs(mock.Anything, "user1").Return(dbOrgs, len(dbOrgs), nil).Once()
@@ -127,6 +135,12 @@ func TestOrganizationComponent_Index(t *testing.T) {
127135
if expectedOrgs[i].Verified != dbOrgs[i].Verified {
128136
return false
129137
}
138+
if expectedOrgs[i].Namespace == nil {
139+
return false
140+
}
141+
if expectedOrgs[i].Namespace.Path != dbOrgs[i].Namespace.Path {
142+
return false
143+
}
130144
}
131145
return true
132146
})

0 commit comments

Comments
 (0)