Search: ensure that postgres nulls are handled correctly for users and sessions

This commit is contained in:
Keith Martin 2026-07-11 13:08:49 +10:00
parent 08c9259824
commit b6de11b1df
4 changed files with 67 additions and 1 deletions

View file

@ -1,6 +1,8 @@
package entity
import (
"time"
"gorm.io/gorm"
"github.com/photoprism/photoprism/internal/auth/acl"
@ -203,6 +205,7 @@ var UserFixtures = UserMap{
BirthMonth: 5,
BirthYear: 2001,
},
LoginAt: TimePointer(Now().Add(time.Second * -24)),
},
"guest": {
ID: 10000025,
@ -292,6 +295,7 @@ var UserFixtures = UserMap{
WebDAV: false,
CanInvite: false,
InviteToken: "",
LoginAt: TimePointer(Now().Add(time.Millisecond * -24)),
},
}

View file

@ -8,6 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/entity/sortby"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/dsn"
)
// Sessions finds user sessions.
@ -65,7 +66,11 @@ func Sessions(frm form.SearchSessions) (result entity.Sessions, err error) {
Where("client_name <> '' AND client_name IS NOT NULL").
Order(OrderExpr("client_name, created_at, id", frm.Reverse))
case sortby.Login, sortby.LoginAt:
stmt = stmt.Order(OrderExpr("login_at DESC, user_name, client_name, id", frm.Reverse))
if entity.DbDialect() == dsn.DialectPostgreSQL {
stmt = stmt.Order(OrderExpr("login_at DESC NULLS LAST, user_name, client_name, id", frm.Reverse))
} else {
stmt = stmt.Order(OrderExpr("login_at DESC, user_name, client_name, id", frm.Reverse))
}
case sortby.Created, sortby.CreatedAt:
stmt = stmt.Order(OrderExpr("created_at ASC, user_name, client_name, id", frm.Reverse))
case sortby.Updated, sortby.UpdatedAt:

View file

@ -105,6 +105,18 @@ func TestSessions(t *testing.T) {
}
}
})
t.Run("SortByLoginAtReverse", func(t *testing.T) {
if results, err := Sessions(form.SearchSessions{Count: 100, Offset: 2, Order: sortby.LoginAt, Reverse: true}); err != nil {
t.Fatal(err)
} else {
// t.Logf("sessions: %#v", results)
assert.LessOrEqual(t, 1, len(results))
if len(results) > 0 {
assert.Equal(t, "Analytics", results[0].ClientName)
assert.Equal(t, "Invalid", results[1].ClientName)
}
}
})
t.Run("SortByCreatedAt", func(t *testing.T) {
if results, err := Sessions(form.SearchSessions{Count: 100, Order: sortby.CreatedAt}); err != nil {
t.Fatal(err)

View file

@ -167,4 +167,49 @@ func TestUsers(t *testing.T) {
}
}
})
t.Run("SortByLogin", func(t *testing.T) {
if results, err := Users(form.SearchUsers{Count: 100, Offset: 0, Order: sortby.Login}); err != nil {
t.Fatal(err)
} else {
// t.Logf("users: %#v", results)
assert.LessOrEqual(t, 1, len(results))
if len(results) > 0 {
assert.Equal(t, "Gandalf", results[0].DisplayName)
assert.Equal(t, "Jane Dow", results[1].DisplayName)
}
}
})
t.Run("SortByLoginReversed", func(t *testing.T) {
if results, err := Users(form.SearchUsers{Count: 100, Offset: 0, Order: sortby.Login, Reverse: true}); err != nil {
t.Fatal(err)
} else {
// t.Logf("users: %#v", results)
assert.LessOrEqual(t, 1, len(results))
if len(results) > 0 {
assert.Equal(t, "2FA Enabled", results[0].DisplayName)
assert.Equal(t, "Not Local", results[1].DisplayName)
}
}
})
t.Run("SortByDeleted", func(t *testing.T) {
if results, err := Users(form.SearchUsers{Count: 100, Offset: 0, Order: sortby.Deleted, Deleted: true}); err != nil {
t.Fatal(err)
} else {
// t.Logf("users: %#v", results)
if assert.Equal(t, 1, len(results)) {
assert.Equal(t, "deleted", results[0].UserName)
}
}
})
t.Run("SortByDeletedReversed", func(t *testing.T) {
if results, err := Users(form.SearchUsers{Count: 100, Offset: 0, Order: sortby.Deleted, Reverse: true, Deleted: true}); err != nil {
t.Fatal(err)
} else {
// t.Logf("users: %#v", results)
if assert.Equal(t, 1, len(results)) {
assert.Equal(t, "deleted", results[0].UserName)
}
}
})
}