mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-31 08:11:27 +02:00
fix: make sorting of strings case insensitive
This commit is contained in:
@@ -9,8 +9,8 @@ import (
|
||||
type API struct {
|
||||
model.Base
|
||||
|
||||
Name string `sortable:"true"`
|
||||
Audience string `sortable:"true"`
|
||||
Name string `sortable:"case-insensitive"`
|
||||
Audience string `sortable:"case-insensitive"`
|
||||
UpdatedAt *datatype.DateTime
|
||||
AllowCIMDClients bool `gorm:"column:allow_cimd_clients"`
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
type ApiKey struct {
|
||||
model.Base
|
||||
|
||||
Name string `sortable:"true"`
|
||||
Name string `sortable:"case-insensitive"`
|
||||
Key string
|
||||
Description *string
|
||||
ExpiresAt datatype.DateTime `sortable:"true"`
|
||||
|
||||
@@ -46,7 +46,7 @@ const (
|
||||
type OidcClient struct {
|
||||
Base
|
||||
|
||||
Name string `sortable:"true"`
|
||||
Name string `sortable:"case-insensitive"`
|
||||
Description string
|
||||
CallbackURLs datatype.StringList
|
||||
LogoutCallbackURLs datatype.StringList
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
type User struct {
|
||||
Base
|
||||
|
||||
Username string `sortable:"true"`
|
||||
Email *string `sortable:"true"`
|
||||
Username string `sortable:"case-insensitive"`
|
||||
Email *string `sortable:"case-insensitive"`
|
||||
EmailVerified bool `sortable:"true" filterable:"true"`
|
||||
FirstName string `sortable:"true"`
|
||||
LastName string `sortable:"true"`
|
||||
DisplayName string `sortable:"true"`
|
||||
FirstName string `sortable:"case-insensitive"`
|
||||
LastName string `sortable:"case-insensitive"`
|
||||
DisplayName string `sortable:"case-insensitive"`
|
||||
IsAdmin bool `sortable:"true" filterable:"true"`
|
||||
Locale *string
|
||||
LdapID *string
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
type UserGroup struct {
|
||||
Base
|
||||
FriendlyName string `sortable:"true"`
|
||||
Name string `sortable:"true"`
|
||||
FriendlyName string `sortable:"case-insensitive"`
|
||||
Name string `sortable:"case-insensitive"`
|
||||
LdapID *string
|
||||
UpdatedAt *datatype.DateTime
|
||||
Users []User `gorm:"many2many:user_groups_users;"`
|
||||
|
||||
@@ -29,9 +29,10 @@ type ListRequestOptions struct {
|
||||
}
|
||||
|
||||
type FieldMeta struct {
|
||||
ColumnName string
|
||||
IsSortable bool
|
||||
IsFilterable bool
|
||||
ColumnName string
|
||||
IsSortable bool
|
||||
IsFilterable bool
|
||||
IsCaseInsensitive bool
|
||||
}
|
||||
|
||||
func ParseListRequestOptions(ctx *gin.Context) (listRequestOptions ListRequestOptions) {
|
||||
@@ -155,6 +156,18 @@ func applySorting(sortColumn string, sortDirection string, query *gorm.DB, meta
|
||||
}
|
||||
|
||||
sortDirection = NormalizeSortDirection(sortDirection)
|
||||
if fieldMeta.IsCaseInsensitive {
|
||||
expression := "LOWER(?)"
|
||||
if sortDirection == "desc" {
|
||||
expression += " DESC"
|
||||
}
|
||||
return query.Clauses(clause.OrderBy{
|
||||
Expression: clause.Expr{
|
||||
SQL: expression,
|
||||
Vars: []any{clause.Column{Name: fieldMeta.ColumnName}},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
query = query.Clauses(clause.OrderBy{
|
||||
Columns: []clause.OrderByColumn{
|
||||
@@ -191,10 +204,12 @@ func extractModelMetadata(model any) map[string]FieldMeta {
|
||||
|
||||
// Normal field: record metadata
|
||||
name := field.Name
|
||||
sortMode := field.Tag.Get("sortable")
|
||||
meta[name] = FieldMeta{
|
||||
ColumnName: CamelCaseToSnakeCase(name),
|
||||
IsSortable: field.Tag.Get("sortable") == "true",
|
||||
IsFilterable: field.Tag.Get("filterable") == "true",
|
||||
ColumnName: CamelCaseToSnakeCase(name),
|
||||
IsSortable: sortMode == "true" || sortMode == "case-insensitive",
|
||||
IsFilterable: field.Tag.Get("filterable") == "true",
|
||||
IsCaseInsensitive: sortMode == "case-insensitive",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
backend/internal/utils/list_request_util_test.go
Normal file
52
backend/internal/utils/list_request_util_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/libtnb/sqlite"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestPaginateFilterAndSortSortsStringsCaseInsensitively(t *testing.T) {
|
||||
type record struct {
|
||||
ID uint
|
||||
Name string `sortable:"case-insensitive"`
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file:"+CreateSha256Hash(t.Name())+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.Exec("CREATE TABLE records (id INTEGER PRIMARY KEY, name TEXT NOT NULL)").Error)
|
||||
require.NoError(t, db.Create(&[]record{{Name: "alpha"}, {Name: "Charlie"}, {Name: "bravo"}}).Error)
|
||||
|
||||
for _, test := range []struct {
|
||||
direction string
|
||||
expected []string
|
||||
}{
|
||||
{direction: "asc", expected: []string{"alpha", "bravo", "Charlie"}},
|
||||
{direction: "desc", expected: []string{"Charlie", "bravo", "alpha"}},
|
||||
} {
|
||||
t.Run(test.direction, func(t *testing.T) {
|
||||
options := ListRequestOptions{}
|
||||
options.Sort.Column = "name"
|
||||
options.Sort.Direction = test.direction
|
||||
|
||||
var records []record
|
||||
_, err := PaginateFilterAndSort(options, db.Model(&record{}), &records)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, records, 3)
|
||||
require.Equal(t, test.expected, []string{records[0].Name, records[1].Name, records[2].Name})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractModelMetadataRequiresCaseInsensitiveSortMode(t *testing.T) {
|
||||
type record struct {
|
||||
Name string `sortable:"case-insensitive"`
|
||||
Event string `sortable:"true"`
|
||||
}
|
||||
|
||||
meta := extractModelMetadata(&[]record{})
|
||||
require.True(t, meta["Name"].IsCaseInsensitive)
|
||||
require.False(t, meta["Event"].IsCaseInsensitive)
|
||||
}
|
||||
Reference in New Issue
Block a user