-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(server): sort duplicate key error #55
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
help: | ||
@echo "Usage:" | ||
@echo " make <target>" | ||
@echo "" | ||
@echo "Targets:" | ||
@echo " lint Run golangci-lint with auto-fix" | ||
@echo " test Run unit tests with race detector in short mode" | ||
|
||
lint: | ||
golangci-lint run --fix | ||
|
||
test: | ||
go test -race -short -v ./... | ||
|
||
.PHONY: lint test |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package workspace | |
|
||
import ( | ||
"sort" | ||
"sync" | ||
|
||
"github.com/reearth/reearthx/account/accountdomain/user" | ||
"github.com/reearth/reearthx/i18n" | ||
|
@@ -29,6 +30,7 @@ type Members struct { | |
users map[UserID]Member | ||
integrations map[IntegrationID]Member | ||
fixed bool | ||
mu sync.Mutex | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider using sync.RWMutex for better performance Since the struct has both read and write operations, using Apply this change: - mu sync.Mutex
+ mu sync.RWMutex Then use func (m *Members) Users() map[UserID]Member {
m.mu.RLock()
defer m.mu.RUnlock()
return maps.Clone(m.users)
} This pattern should be applied to all read-only methods:
|
||
} | ||
|
||
func NewMembers() *Members { | ||
|
@@ -221,6 +223,9 @@ func (m *Members) AddIntegration(iid IntegrationID, role Role, i UserID) error { | |
} | ||
|
||
func (m *Members) Leave(u UserID) error { | ||
m.mu.Lock() | ||
defer m.mu.Unlock() | ||
|
||
if m.fixed { | ||
return ErrCannotModifyPersonalWorkspace | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |||||||||||||||||||||||||||||
"github.com/reearth/reearthx/rerror" | ||||||||||||||||||||||||||||||
"github.com/reearth/reearthx/usecasex" | ||||||||||||||||||||||||||||||
"go.mongodb.org/mongo-driver/bson" | ||||||||||||||||||||||||||||||
"go.mongodb.org/mongo-driver/bson/primitive" | ||||||||||||||||||||||||||||||
"go.mongodb.org/mongo-driver/mongo" | ||||||||||||||||||||||||||||||
"go.mongodb.org/mongo-driver/mongo/options" | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
|
@@ -40,8 +41,15 @@ func (c *Collection) Paginate(ctx context.Context, rawFilter any, s *usecasex.So | |||||||||||||||||||||||||||||
sortOrder *= -1 | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
var sort primitive.D | ||||||||||||||||||||||||||||||
if sortKey == idKey { | ||||||||||||||||||||||||||||||
sort = bson.D{{Key: sortKey, Value: sortOrder}} | ||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||
sort = bson.D{{Key: sortKey, Value: sortOrder}, {Key: idKey, Value: sortOrder}} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
findOpts := options.Find(). | ||||||||||||||||||||||||||||||
SetSort(bson.D{{Key: sortKey, Value: sortOrder}, {Key: idKey, Value: sortOrder}}). | ||||||||||||||||||||||||||||||
SetSort(sort). | ||||||||||||||||||||||||||||||
SetLimit(limit(*p)) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if p.Offset != nil { | ||||||||||||||||||||||||||||||
|
@@ -199,11 +207,11 @@ func (c *Collection) aggregateFilter(ctx context.Context, p usecasex.Pagination, | |||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
func aggregateOptionsFromPagination(_ usecasex.Pagination, _ *usecasex.Sort) *options.AggregateOptions { | ||||||||||||||||||||||||||||||
collation := options.Collation{ | ||||||||||||||||||||||||||||||
Locale: "en", | ||||||||||||||||||||||||||||||
Strength: 2, | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return options.Aggregate().SetAllowDiskUse(true).SetCollation(&collation) | ||||||||||||||||||||||||||||||
collation := options.Collation{ | ||||||||||||||||||||||||||||||
Locale: "en", | ||||||||||||||||||||||||||||||
Strength: 2, | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return options.Aggregate().SetAllowDiskUse(true).SetCollation(&collation) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
func (c *Collection) pageFilter(ctx context.Context, p usecasex.Pagination, s *usecasex.Sort) (bson.M, error) { | ||||||||||||||||||||||||||||||
|
@@ -280,11 +288,11 @@ func (c *Collection) getCursorDocument(ctx context.Context, cursor usecasex.Curs | |||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
func sortFilter(p usecasex.Pagination, s *usecasex.Sort) bson.D { | ||||||||||||||||||||||||||||||
var sortOptions bson.D | ||||||||||||||||||||||||||||||
if s != nil && s.Key != "" && s.Key != idKey { | ||||||||||||||||||||||||||||||
sortOptions = append(sortOptions, bson.E{Key: s.Key, Value: sortDirection(p, s)}) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return append(sortOptions, bson.E{Key: idKey, Value: sortDirection(p, s)}) | ||||||||||||||||||||||||||||||
var sortOptions bson.D | ||||||||||||||||||||||||||||||
if s != nil && s.Key != "" && s.Key != idKey { | ||||||||||||||||||||||||||||||
sortOptions = append(sortOptions, bson.E{Key: s.Key, Value: sortDirection(p, s)}) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
return append(sortOptions, bson.E{Key: idKey, Value: sortDirection(p, s)}) | ||||||||||||||||||||||||||||||
Comment on lines
+291
to
+295
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix potential nil pointer dereference The condition func sortFilter(p usecasex.Pagination, s *usecasex.Sort) bson.D {
var sortOptions bson.D
if s != nil && s.Key != "" && s.Key != idKey {
- sortOptions = append(sortOptions, bson.E{Key: s.Key, Value: sortDirection(p, s)})
+ direction := sortDirection(p, s)
+ sortOptions = append(sortOptions, bson.E{Key: s.Key, Value: direction})
+ return append(sortOptions, bson.E{Key: idKey, Value: direction})
}
- return append(sortOptions, bson.E{Key: idKey, Value: sortDirection(p, s)})
+ return bson.D{{Key: idKey, Value: sortDirection(p, nil)}}
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
func limit(p usecasex.Pagination) int64 { | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure consistent mutex usage across all map operations
While adding mutex protection is good, the current implementation only protects the
Leave
method. Other methods that access or modify theusers
andintegrations
maps should also be protected to ensure thread safety.The following methods need mutex protection:
Join
AddIntegration
DeleteIntegration
UpdateUserRole
UpdateIntegrationRole
Example pattern to follow:
Also applies to: 33-33