Skip to content

Commit

Permalink
implement generic pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
videah committed Jan 16, 2024
1 parent 94370ec commit 6e5476a
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 21 deletions.
5 changes: 5 additions & 0 deletions lib/models/params/notification_params.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class NotificationParams {
this.types,
this.excludeTypes,
this.accountId,
this.cursor,
});

/// Converts JSON into a [NotificationParams] instance.
Expand Down Expand Up @@ -49,4 +50,8 @@ class NotificationParams {
/// Return only notifications related from this account.
@JsonKey(name: 'account_id')
final String? accountId;

/// Arbitrary cursor string. This is not present in the Mastodon API, and
/// is only used by SkyBridge to make pagination easier.
final String? cursor;
}
2 changes: 2 additions & 0 deletions lib/models/params/notification_params.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion lib/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ String sanitizeText(String text) {
/// [Uri.toString] lowercases the host, which breaks the URI
/// for some clients. This is just a simple function that preserves the casing.
String stringifyModifiedUri(Uri uri, String originalUri) {
final host = originalUri.substring(0, uri.scheme.length + uri.host.length + 3);
final host =
originalUri.substring(0, uri.scheme.length + uri.host.length + 3);
return host + uri.toString().substring(host.length);
}

Map<String, String> generatePaginationHeaders<T>({
required List<T> items,
required Uri requestUri,
required String nextCursor,
required BigInt Function(T) getId,
}) {
final highestID = items.map(getId).reduce((a, b) => a > b ? a : b);
final prevURI = requestUri.replace(queryParameters: {'min_id': highestID.toString()});
final nextURI = requestUri.replace(queryParameters: {'cursor': nextCursor});

return {'Link': '<$prevURI>; rel="prev", <$nextURI>; rel="next"'};
}
13 changes: 13 additions & 0 deletions routes/api/v1/notifications/index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ Future<Response> onRequest<T>(RequestContext context) async {
// Fetch the notifications with the given parameters.
final response = await bluesky.notification.listNotifications(
limit: limit,
cursor: encodedParams.cursor,
);
final nextCursor = response.data.cursor;

// Convert the response into a list of MastodonNotification objects with
// the appropriate types and data.
Expand All @@ -36,7 +38,18 @@ Future<Response> onRequest<T>(RequestContext context) async {
bluesky,
);

var headers = <String, String>{};
if (notifs.isNotEmpty) {
headers = generatePaginationHeaders(
items: notifs,
requestUri: context.request.uri,
nextCursor: nextCursor ?? '',
getId: (notification) => BigInt.parse(notification.id),
);
}

return threadedJsonResponse(
body: notifs,
headers: headers,
);
}
36 changes: 16 additions & 20 deletions routes/api/v1/timelines/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,27 +90,23 @@ Future<Response> onRequest(RequestContext context) async {
// Get the parent posts for each post.
final processedPosts = await processParentPosts(bluesky, allPosts);

final headers = <String, Object>{};
if (processedPosts.isNotEmpty && backfillAllowed == 'true') {
final uri = context.request.uri;

var lowestID = BigInt.parse('99999999999999999999');
var highestID = BigInt.from(0);
for (final post in processedPosts) {
final id = BigInt.parse(post.id);
if (id < lowestID) {
lowestID = id;
}
if (id > highestID) {
highestID = id;
}
}
var headers = <String, String>{};
if (processedPosts.isNotEmpty) {
headers = generatePaginationHeaders(
items: processedPosts,
requestUri: context.request.uri,
nextCursor: nextCursor ?? '',
getId: (post) => BigInt.parse(post.id),
);

final prevParams = {'min_id': highestID.toString()};
final nextParams = {'cursor': nextCursor};
final prevURI = uri.replace(queryParameters: prevParams);
final nextURI = uri.replace(queryParameters: nextParams);
headers['Link'] = '<$prevURI>; rel="prev", <$nextURI>; rel="next"';
// final ids = processedPosts.map((post) => BigInt.parse(post.id)).toList();
// final highestID = ids.reduce((a, b) => a > b ? a : b);
//
// final prevParams = {'min_id': highestID.toString()};
// final nextParams = {'cursor': nextCursor};
// final prevURI = uri.replace(queryParameters: prevParams);
// final nextURI = uri.replace(queryParameters: nextParams);
// headers['Link'] = '<$prevURI>; rel="prev", <$nextURI>; rel="next"';
}

// If the user prefers not to see replies, we need to filter them out.
Expand Down

0 comments on commit 6e5476a

Please sign in to comment.