Auto-indexing service and GraphQL API for AT Protocol Records quickslice.slices.network/
atproto gleam graphql

fix(notifications): decode cursor with correct sort_by for 2-part format

+66 -1
+3 -1
server/src/database/repositories/records.gleam
··· 1112 1112 } 1113 1113 1114 1114 // Build cursor clause 1115 + // Notification cursors use rkey|uri format (2 parts) 1116 + let notification_sort = Some([#("rkey", "desc")]) 1115 1117 let #(cursor_clause, cursor_params) = case after { 1116 1118 None -> #("", []) 1117 1119 Some(cursor) -> { 1118 - case pagination.decode_cursor(cursor, None) { 1120 + case pagination.decode_cursor(cursor, notification_sort) { 1119 1121 Ok(decoded) -> { 1120 1122 // Cursor format: rkey|uri for TID-based chronological sorting 1121 1123 let rkey_value =
+63
server/test/database/repositories/notifications_test.gleam
··· 94 94 let assert Ok(first): Result(Record, _) = list.first(results) 95 95 first.collection |> should.equal("app.bsky.feed.like") 96 96 } 97 + 98 + pub fn get_notifications_pagination_test() { 99 + let assert Ok(db) = test_helpers.create_test_db() 100 + let assert Ok(_) = test_helpers.create_record_table(db) 101 + 102 + // Insert 3 records mentioning target with TID-like rkeys for predictable ordering 103 + // TIDs sort lexicographically, so "3aaa" < "3bbb" < "3ccc" 104 + let assert Ok(_) = 105 + records.insert( 106 + db, 107 + "at://did:plc:author/app.bsky.feed.like/3aaaaaa", 108 + "bafy1", 109 + "did:plc:author", 110 + "app.bsky.feed.like", 111 + "{\"subject\":\"at://did:plc:target/post/1\"}", 112 + ) 113 + 114 + let assert Ok(_) = 115 + records.insert( 116 + db, 117 + "at://did:plc:author/app.bsky.feed.like/3bbbbbbb", 118 + "bafy2", 119 + "did:plc:author", 120 + "app.bsky.feed.like", 121 + "{\"subject\":\"at://did:plc:target/post/2\"}", 122 + ) 123 + 124 + let assert Ok(_) = 125 + records.insert( 126 + db, 127 + "at://did:plc:author/app.bsky.feed.like/3ccccccc", 128 + "bafy3", 129 + "did:plc:author", 130 + "app.bsky.feed.like", 131 + "{\"subject\":\"at://did:plc:target/post/3\"}", 132 + ) 133 + 134 + // Get first page with limit 2 135 + let assert Ok(#(page1, cursor1, has_next1, _)) = 136 + records.get_notifications(db, "did:plc:target", None, Some(2), None) 137 + 138 + // Should have 2 results, sorted by rkey DESC (3ccc, 3bbb) 139 + list.length(page1) |> should.equal(2) 140 + has_next1 |> should.be_true 141 + 142 + let assert Ok(first) = list.first(page1) 143 + first.rkey |> should.equal("3ccccccc") 144 + 145 + let assert Ok(second) = page1 |> list.drop(1) |> list.first 146 + second.rkey |> should.equal("3bbbbbbb") 147 + 148 + // Use cursor to get next page 149 + let assert Some(cursor) = cursor1 150 + let assert Ok(#(page2, _cursor2, has_next2, _)) = 151 + records.get_notifications(db, "did:plc:target", None, Some(2), Some(cursor)) 152 + 153 + // Should have 1 result (3aaa) 154 + list.length(page2) |> should.equal(1) 155 + has_next2 |> should.be_false 156 + 157 + let assert Ok(third) = list.first(page2) 158 + third.rkey |> should.equal("3aaaaaa") 159 + }