Add gateway realtime fanout and cursor pagination docs
This commit is contained in:
1 parent
4399eafc90
commit
ccf966ffc1
5 files changed
+800
-23
No files matched your search
@@ -3,14 +3,20 @@ import 'package:chat_server/objectbox.g.dart';
|
||||
import 'package:relic/relic.dart';
|
||||
|
||||
import '../auth/auth_middleware.dart';
|
||||
import '../gateway/gateway_handlers.dart';
|
||||
import '../state/app_state.dart';
|
||||
import '../utils/extensions.dart';
|
||||
|
||||
const _defaultMessageLimit = 50;
|
||||
const _maxMessageLimit = 100;
|
||||
|
||||
/// Get messages for a channel
|
||||
/// Query params: channelId (required), limit, before
|
||||
/// Query params: channelId (required), limit, before, after
|
||||
Future<Response> getMessagesHandler(Request req) async {
|
||||
final channelIdStr = req.url.queryParameters['channelId'];
|
||||
final limitStr = req.url.queryParameters['limit'] ?? '50';
|
||||
final limitStr = req.url.queryParameters['limit'];
|
||||
final beforeStr = req.url.queryParameters['before'];
|
||||
final afterStr = req.url.queryParameters['after'];
|
||||
|
||||
if (channelIdStr == null) {
|
||||
return Response.badRequest(
|
||||
@@ -18,8 +24,17 @@ Future<Response> getMessagesHandler(Request req) async {
|
||||
);
|
||||
}
|
||||
|
||||
if (beforeStr != null && afterStr != null) {
|
||||
return Response.badRequest(
|
||||
body: Body.fromString('Use either `before` or `after`, not both'),
|
||||
);
|
||||
}
|
||||
|
||||
final channelId = int.tryParse(channelIdStr);
|
||||
final limit = int.tryParse(limitStr) ?? 50;
|
||||
final parsedLimit =
|
||||
limitStr == null ? _defaultMessageLimit : int.tryParse(limitStr);
|
||||
int? before;
|
||||
int? after;
|
||||
|
||||
if (channelId == null) {
|
||||
return Response.badRequest(
|
||||
@@ -27,6 +42,32 @@ Future<Response> getMessagesHandler(Request req) async {
|
||||
);
|
||||
}
|
||||
|
||||
if (parsedLimit == null || parsedLimit <= 0) {
|
||||
return Response.badRequest(
|
||||
body: Body.fromString('Invalid limit'),
|
||||
);
|
||||
}
|
||||
|
||||
final limit = parsedLimit > _maxMessageLimit ? _maxMessageLimit : parsedLimit;
|
||||
|
||||
if (beforeStr != null) {
|
||||
before = int.tryParse(beforeStr);
|
||||
if (before == null || before <= 0) {
|
||||
return Response.badRequest(
|
||||
body: Body.fromString('Invalid `before` cursor'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (afterStr != null) {
|
||||
after = int.tryParse(afterStr);
|
||||
if (after == null || after <= 0) {
|
||||
return Response.badRequest(
|
||||
body: Body.fromString('Invalid `after` cursor'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check channel exists
|
||||
final channel = channelBox.get(channelId);
|
||||
if (channel == null) {
|
||||
@@ -35,33 +76,50 @@ Future<Response> getMessagesHandler(Request req) async {
|
||||
);
|
||||
}
|
||||
|
||||
// Get messages
|
||||
final query = messageBox
|
||||
.query(ChatMessage_.channelId.equals(channelId))
|
||||
.order(ChatMessage_.createdAt, flags: Order.descending)
|
||||
.build();
|
||||
var condition = ChatMessage_.channelId.equals(channelId);
|
||||
if (before != null) {
|
||||
condition = condition & ChatMessage_.id.lessThan(before);
|
||||
} else if (after != null) {
|
||||
condition = condition & ChatMessage_.id.greaterThan(after);
|
||||
}
|
||||
|
||||
query.limit = limit;
|
||||
final messages = query.find();
|
||||
final queryBuilder = messageBox.query(condition);
|
||||
if (after != null) {
|
||||
queryBuilder.order(ChatMessage_.id);
|
||||
} else {
|
||||
queryBuilder.order(ChatMessage_.id, flags: Order.descending);
|
||||
}
|
||||
|
||||
final query = queryBuilder.build();
|
||||
query.limit = limit + 1;
|
||||
|
||||
final fetchedMessages = query.find();
|
||||
query.close();
|
||||
|
||||
final hasMore = fetchedMessages.length > limit;
|
||||
final messages =
|
||||
hasMore ? fetchedMessages.sublist(0, limit) : fetchedMessages;
|
||||
|
||||
int? nextBefore;
|
||||
int? nextAfter;
|
||||
if (messages.isNotEmpty) {
|
||||
if (after != null) {
|
||||
nextBefore = messages.first.id;
|
||||
nextAfter = messages.last.id;
|
||||
} else {
|
||||
nextBefore = messages.last.id;
|
||||
nextAfter = messages.first.id;
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(
|
||||
body: {
|
||||
'messages': messages
|
||||
.map((m) => {
|
||||
'id': m.id,
|
||||
'content': m.content,
|
||||
'authorId': m.authorUserId,
|
||||
'authorUsername': m.authorUsername,
|
||||
'authorAvatarUrl': m.authorAvatarUrl,
|
||||
'authorHomeServer': m.authorHomeServer,
|
||||
'channelId': m.channelId,
|
||||
'createdAt': m.createdAt.toIso8601String(),
|
||||
'editedAt': m.editedAt?.toIso8601String(),
|
||||
})
|
||||
.toList(),
|
||||
'messages': messages.map(_messageToMap).toList(),
|
||||
'channelId': channelId,
|
||||
'limit': limit,
|
||||
'hasMore': hasMore,
|
||||
'nextBefore': nextBefore,
|
||||
'nextAfter': nextAfter,
|
||||
}.toBody,
|
||||
);
|
||||
}
|
||||
@@ -123,6 +181,12 @@ Future<Response> sendMessageHandler(Request req) async {
|
||||
);
|
||||
|
||||
final id = messageBox.put(message);
|
||||
message.id = id;
|
||||
|
||||
broadcastMessageCreated(
|
||||
channelId: channelId,
|
||||
message: _messageToMap(message),
|
||||
);
|
||||
|
||||
return Response.ok(
|
||||
body: {
|
||||
@@ -192,6 +256,11 @@ Future<Response> editMessageHandler(Request req) async {
|
||||
message.editedAt = DateTime.now();
|
||||
messageBox.put(message);
|
||||
|
||||
broadcastMessageUpdated(
|
||||
channelId: message.channelId,
|
||||
message: _messageToMap(message),
|
||||
);
|
||||
|
||||
return Response.ok(
|
||||
body: {
|
||||
'id': message.id,
|
||||
@@ -243,8 +312,14 @@ Future<Response> deleteMessageHandler(Request req) async {
|
||||
);
|
||||
}
|
||||
|
||||
final channelId = message.channelId;
|
||||
messageBox.remove(messageId);
|
||||
|
||||
broadcastMessageDeleted(
|
||||
channelId: channelId,
|
||||
messageId: messageId,
|
||||
);
|
||||
|
||||
return Response.ok(
|
||||
body: {
|
||||
'deleted': true,
|
||||
@@ -252,3 +327,17 @@ Future<Response> deleteMessageHandler(Request req) async {
|
||||
}.toBody,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _messageToMap(ChatMessage message) {
|
||||
return {
|
||||
'id': message.id,
|
||||
'content': message.content,
|
||||
'authorId': message.authorUserId,
|
||||
'authorUsername': message.authorUsername,
|
||||
'authorAvatarUrl': message.authorAvatarUrl,
|
||||
'authorHomeServer': message.authorHomeServer,
|
||||
'channelId': message.channelId,
|
||||
'createdAt': message.createdAt.toIso8601String(),
|
||||
'editedAt': message.editedAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user