Merge branch 'develop' into fix/message-search-pagination

This commit is contained in:
Sahil Kumar
2021-09-08 15:51:33 +05:30
committed by GitHub
49 changed files with 1433 additions and 321 deletions
@@ -107,6 +107,48 @@ The 'exists' filter matches values that exist, or don't exist, based on the spec
Filter.exists('name', true) Filter.exists('name', true)
``` ```
#### Filter.contains
The 'contains' filter matches any list that contains the specified value.
```dart
Filter.contains('teams', 'red')
```
#### Filter.empty
The 'empty' filter constructor returns an empty filter. It's the equivalent of an empty map `{}`;
```dart
Filter.empty();
```
#### Filter.raw
The 'raw' filter constructor lets you specify a raw filter. We suggest using this only if you can't manage to build what you want using the other constructors.
```dart
Filter.raw(value: {
'members': [
..._selectedUsers.map((e) => e.id),
chatState.currentUser!.id,
],
'distinct': true,
});
```
#### Filter.custom
The 'custom' filter is used to create a custom filter in case it does not exists or it's not been added to the SDK yet.
Note that the filter must be supported by the Stream backend in order to work.
```dart
Filter.custom(
operator: '\$max',
value: 10,
)
```
### Group Queries ### Group Queries
#### Filter.and #### Filter.and
+13 -1
View File
@@ -1,8 +1,20 @@
## Upcoming ## Upcoming
🛑️ Breaking Changes from `2.2.1`
- Added 6 new methods in `ChatPersistenceClient`.
- `bulkUpdateMessages`
- `bulkUpdatePinnedMessages`
- `bulkUpdateMembers`
- `bulkUpdateReads`
- `updatePinnedMessageReactions`
- `deletePinnedMessageReactionsByMessageId`
✅ Added ✅ Added
- Add support for `next`, `previous` value pagination in `client.search`, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination) - Added `Filter.contains` and `Filter.empty`
- Added support for `next`, `previous` value pagination in `client.search`
, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination)
## 2.2.1 ## 2.2.1
@@ -51,6 +51,9 @@ enum FilterOperator {
/// Matches none of the values specified in an array. /// Matches none of the values specified in an array.
nor, nor,
/// Matches any list that contains the specified value
contains,
} }
/// Helper extension for [FilterOperator] /// Helper extension for [FilterOperator]
@@ -71,6 +74,7 @@ extension FilterOperatorX on FilterOperator {
FilterOperator.and: '\$and', FilterOperator.and: '\$and',
FilterOperator.or: '\$or', FilterOperator.or: '\$or',
FilterOperator.nor: '\$nor', FilterOperator.nor: '\$nor',
FilterOperator.contains: '\$contains',
}[this]!; }[this]!;
} }
@@ -157,6 +161,10 @@ class Filter extends Equatable {
factory Filter.exists(String key, {bool exists = true}) => factory Filter.exists(String key, {bool exists = true}) =>
Filter._(operator: FilterOperator.exists, key: key, value: exists); Filter._(operator: FilterOperator.exists, key: key, value: exists);
/// Matches any list that contains the specified values
factory Filter.contains(String key, Object value) =>
Filter._(operator: FilterOperator.contains, key: key, value: value);
/// Creates a custom [Filter] if there isn't one already available. /// Creates a custom [Filter] if there isn't one already available.
const factory Filter.custom({ const factory Filter.custom({
required Object value, required Object value,
@@ -164,6 +172,9 @@ class Filter extends Equatable {
String? key, String? key,
}) = Filter.__; }) = Filter.__;
/// An empty filter
factory Filter.empty() => const Filter.raw(value: {});
/// Creates a custom [Filter] from a raw map value /// Creates a custom [Filter] from a raw map value
/// ///
/// ```dart /// ```dart
@@ -143,11 +143,19 @@ abstract class ChatPersistenceClient {
/// Updates the message data of a particular channel [cid] with /// Updates the message data of a particular channel [cid] with
/// the new [messages] data /// the new [messages] data
Future<void> updateMessages(String cid, List<Message> messages); Future<void> updateMessages(String cid, List<Message> messages) =>
bulkUpdateMessages({cid: messages});
/// Bulk updates the message data of multiple channels.
Future<void> bulkUpdateMessages(Map<String, List<Message>> messages);
/// Updates the pinned message data of a particular channel [cid] with /// Updates the pinned message data of a particular channel [cid] with
/// the new [messages] data /// the new [messages] data
Future<void> updatePinnedMessages(String cid, List<Message> messages); Future<void> updatePinnedMessages(String cid, List<Message> messages) =>
bulkUpdatePinnedMessages({cid: messages});
/// Bulk updates the message data of multiple channels.
Future<void> bulkUpdatePinnedMessages(Map<String, List<Message>> messages);
/// Returns all the threads by parent message of a particular channel by /// Returns all the threads by parent message of a particular channel by
/// providing channel [cid] /// providing channel [cid]
@@ -158,11 +166,19 @@ abstract class ChatPersistenceClient {
/// Updates all the members of a particular channle [cid] /// Updates all the members of a particular channle [cid]
/// with the new [members] data /// with the new [members] data
Future<void> updateMembers(String cid, List<Member> members); Future<void> updateMembers(String cid, List<Member> members) =>
bulkUpdateMembers({cid: members});
/// Bulk updates the members data of multiple channels.
Future<void> bulkUpdateMembers(Map<String, List<Member>> members);
/// Updates the read data of a particular channel [cid] with /// Updates the read data of a particular channel [cid] with
/// the new [reads] data /// the new [reads] data
Future<void> updateReads(String cid, List<Read> reads); Future<void> updateReads(String cid, List<Read> reads) =>
bulkUpdateReads({cid: reads});
/// Bulk updates the read data of multiple channels.
Future<void> bulkUpdateReads(Map<String, List<Read>> reads);
/// Updates the users data with the new [users] data /// Updates the users data with the new [users] data
Future<void> updateUsers(List<User> users); Future<void> updateUsers(List<User> users);
@@ -170,9 +186,15 @@ abstract class ChatPersistenceClient {
/// Updates the reactions data with the new [reactions] data /// Updates the reactions data with the new [reactions] data
Future<void> updateReactions(List<Reaction> reactions); Future<void> updateReactions(List<Reaction> reactions);
/// Updates the pinned message reactions data with the new [reactions] data
Future<void> updatePinnedMessageReactions(List<Reaction> reactions);
/// Deletes all the reactions by [messageIds] /// Deletes all the reactions by [messageIds]
Future<void> deleteReactionsByMessageId(List<String> messageIds); Future<void> deleteReactionsByMessageId(List<String> messageIds);
/// Deletes all the pinned messages reactions by [messageIds]
Future<void> deletePinnedMessageReactionsByMessageId(List<String> messageIds);
/// Deletes all the members by channel [cids] /// Deletes all the members by channel [cids]
Future<void> deleteMembersByCids(List<String> cids); Future<void> deleteMembersByCids(List<String> cids);
@@ -182,85 +204,92 @@ abstract class ChatPersistenceClient {
/// Update list of channel states /// Update list of channel states
Future<void> updateChannelStates(List<ChannelState> channelStates) async { Future<void> updateChannelStates(List<ChannelState> channelStates) async {
final deleteReactions = deleteReactionsByMessageId(channelStates final reactionsToDelete = <String>[];
.expand((it) => it.messages) final pinnedReactionsToDelete = <String>[];
.map((m) => m.id) final membersToDelete = <String>[];
.toList(growable: false));
final cleanedChannelStates = final channels = <ChannelModel>[];
channelStates.where((it) => it.channel != null); final channelWithMessages = <String, List<Message>>{};
final channelWithPinnedMessages = <String, List<Message>>{};
final channelWithReads = <String, List<Read>>{};
final channelWithMembers = <String, List<Member>>{};
final deleteMembers = deleteMembersByCids( final users = <User>[];
cleanedChannelStates.map((it) => it.channel!.cid).toList(growable: false), final reactions = <Reaction>[];
); final pinnedReactions = <Reaction>[];
for (final state in channelStates) {
final channel = state.channel;
if (channel != null) {
channels.add(channel);
final cid = channel.cid;
final reads = state.read;
final members = state.members;
final messages = state.messages;
final pinnedMessages = state.pinnedMessages;
// Preparing deletion data
membersToDelete.add(cid);
reactionsToDelete.addAll(state.messages.map((it) => it.id));
pinnedReactionsToDelete.addAll(state.pinnedMessages.map((it) => it.id));
// preparing addition data
channelWithReads[cid] = reads;
channelWithMembers[cid] = members;
channelWithMessages[cid] = messages;
channelWithPinnedMessages[cid] = pinnedMessages;
List<Reaction> expandReactions(Message message) {
final own = message.ownReactions;
final latest = message.latestReactions;
return [
if (own != null) ...own.where((r) => r.userId != null),
if (latest != null) ...latest.where((r) => r.userId != null),
];
}
reactions.addAll(messages.expand(expandReactions));
pinnedReactions.addAll(pinnedMessages.expand(expandReactions));
users.addAll([
channel.createdBy,
...reads.map((it) => it.user),
...members.map((it) => it.user),
...reactions.map((it) => it.user),
...pinnedReactions.map((it) => it.user),
].withNullifyer);
}
}
// Removing old members and reactions data as they may have
// changes over the time.
await Future.wait([ await Future.wait([
deleteReactions, deleteMembersByCids(membersToDelete),
deleteMembers, deleteReactionsByMessageId(reactionsToDelete),
deletePinnedMessageReactionsByMessageId(pinnedReactionsToDelete),
]); ]);
final channels = cleanedChannelStates.map((it) => it.channel).withNullifyer; // Updating first as does not depend on any other table.
final reactions = cleanedChannelStates
.expand((it) => it.messages)
.expand((it) => [
if (it.ownReactions != null)
...it.ownReactions!.where((r) => r.userId != null),
if (it.latestReactions != null)
...it.latestReactions!.where((r) => r.userId != null),
])
.withNullifyer;
final users = cleanedChannelStates
.map((cs) => [
cs.channel?.createdBy,
...cs.messages
.map((m) => [
m.user,
if (m.latestReactions != null)
...m.latestReactions!.map((r) => r.user),
if (m.ownReactions != null)
...m.ownReactions!.map((r) => r.user),
])
.expand((v) => v),
...cs.read.map((r) => r.user),
...cs.members.map((m) => m.user),
])
.expand((it) => it)
.withNullifyer;
final updateMessagesFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final messages = it.messages;
return updateMessages(cid, messages.toList(growable: false));
}).toList(growable: false);
final updatePinnedMessagesFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final messages = it.pinnedMessages;
return updatePinnedMessages(cid, messages.toList(growable: false));
}).toList(growable: false);
final updateReadsFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final reads = it.read;
return updateReads(cid, reads.toList(growable: false));
}).toList(growable: false);
final updateMembersFuture = cleanedChannelStates.map((it) {
final cid = it.channel!.cid;
final members = it.members;
return updateMembers(cid, members.toList(growable: false));
}).toList(growable: false);
await Future.wait([ await Future.wait([
...updateMessagesFuture,
...updatePinnedMessagesFuture,
...updateReadsFuture,
...updateMembersFuture,
updateUsers(users.toList(growable: false)), updateUsers(users.toList(growable: false)),
updateChannels(channels.toList(growable: false)), updateChannels(channels.toList(growable: false)),
]);
// All has a foreign key relation with channels table.
await Future.wait([
bulkUpdateReads(channelWithReads),
bulkUpdateMembers(channelWithMembers),
bulkUpdateMessages(channelWithMessages),
bulkUpdatePinnedMessages(channelWithPinnedMessages),
]);
// Both has a foreign key relation with messages, pinnedMessages table.
await Future.wait([
updateReactions(reactions.toList(growable: false)), updateReactions(reactions.toList(growable: false)),
updatePinnedMessageReactions(
pinnedReactions.toList(growable: false),
),
]); ]);
} }
} }
@@ -138,6 +138,20 @@ void main() {
expect(filter.value, value); expect(filter.value, value);
}); });
test('empty', () {
final filter = Filter.empty();
expect(filter.value, {});
});
test('contains', () {
const key = 'testKey';
const values = 'testValue';
final filter = Filter.contains(key, values);
expect(filter.key, key);
expect(filter.value, values);
expect(filter.operator, FilterOperator.contains.rawValue);
});
group('groupedOperator', () { group('groupedOperator', () {
final filter1 = Filter.equal('testKey', 'testValue'); final filter1 = Filter.equal('testKey', 'testValue');
final filter2 = Filter.in_('testKey', const ['testValue']); final filter2 = Filter.in_('testKey', const ['testValue']);
@@ -38,6 +38,11 @@ class TestPersistenceClient extends ChatPersistenceClient {
Future<void> deleteReactionsByMessageId(List<String> messageIds) => Future<void> deleteReactionsByMessageId(List<String> messageIds) =>
Future.value(); Future.value();
@override
Future<void> deletePinnedMessageReactionsByMessageId(
List<String> messageIds) =>
Future.value();
@override @override
Future<void> disconnect({bool flush = false}) => throw UnimplementedError(); Future<void> disconnect({bool flush = false}) => throw UnimplementedError();
@@ -101,26 +106,30 @@ class TestPersistenceClient extends ChatPersistenceClient {
Future<void> updateLastSyncAt(DateTime lastSyncAt) => Future<void> updateLastSyncAt(DateTime lastSyncAt) =>
throw UnimplementedError(); throw UnimplementedError();
@override
Future<void> updateMembers(String cid, List<Member> members) =>
Future.value();
@override
Future<void> updateMessages(String cid, List<Message> messages) =>
Future.value();
@override
Future<void> updatePinnedMessages(String cid, List<Message> messages) =>
Future.value();
@override @override
Future<void> updateReactions(List<Reaction> reactions) => Future.value(); Future<void> updateReactions(List<Reaction> reactions) => Future.value();
@override @override
Future<void> updateReads(String cid, List<Read> reads) => Future.value(); Future<void> updatePinnedMessageReactions(List<Reaction> reactions) =>
Future.value();
@override @override
Future<void> updateUsers(List<User> users) => Future.value(); Future<void> updateUsers(List<User> users) => Future.value();
@override
Future<void> bulkUpdateMembers(Map<String, List<Member>> members) =>
Future.value();
@override
Future<void> bulkUpdateMessages(Map<String, List<Message>> messages) =>
Future.value();
@override
Future<void> bulkUpdatePinnedMessages(Map<String, List<Message>> messages) =>
Future.value();
@override
Future<void> bulkUpdateReads(Map<String, List<Read>> reads) => Future.value();
} }
void main() { void main() {
@@ -4,8 +4,8 @@ import 'package:stream_chat_flutter/src/group_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_image_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image_paint.png)
/// ///
/// It shows the current [Channel] image. /// It shows the current [Channel] image.
/// ///
@@ -8,8 +8,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header_paint.png)
/// ///
/// It shows the current [Channel] information. /// It shows the current [Channel] information.
/// ///
@@ -22,8 +22,8 @@ typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
/// Callback for when 'View Info' is tapped /// Callback for when 'View Info' is tapped
typedef ViewInfoCallback = void Function(Channel); typedef ViewInfoCallback = void Function(Channel);
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_list_view.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_list_view_paint.png)
/// ///
/// It shows the list of current channels. /// It shows the list of current channels.
/// ///
@@ -8,8 +8,8 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_preview_paint.png)
/// ///
/// It shows the current [Channel] preview. /// It shows the current [Channel] preview.
/// ///
@@ -110,11 +110,14 @@ const _kMinMediaPickerSize = 360.0;
const _kDefaultMaxAttachmentSize = 20971520; // 20MB in Bytes const _kDefaultMaxAttachmentSize = 20971520; // 20MB in Bytes
/// Inactive state /// Inactive state
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input.png) ///
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input_paint.png)
///
/// Focused state /// Focused state
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2.png) ///
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_input2_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input2.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_input2_paint.png)
/// ///
/// Widget used to enter the message and add attachments /// Widget used to enter the message and add attachments
/// ///
@@ -91,8 +91,8 @@ class MessageDetails {
final int index; final int index;
} }
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview_paint.png)
/// ///
/// It shows the list of messages of the current channel. /// It shows the list of messages of the current channel.
/// ///
@@ -40,8 +40,8 @@ enum DisplayWidget {
show, show,
} }
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget_paint.png)
/// ///
/// It shows a message with reactions, replies and user avatar. /// It shows a message with reactions, replies and user avatar.
/// ///
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker_paint.png)
/// ///
/// It shows a reaction picker /// It shows a reaction picker
/// ///
@@ -4,8 +4,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/thread_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/thread_header_paint.png)
/// ///
/// It shows the current thread information. /// It shows the current thread information.
/// ///
@@ -1,3 +1,10 @@
## Upcoming
- [[#604]](https://github.com/GetStream/stream-chat-flutter/issues/604) Fix cascade deletion by
enabling `pragma foreign_keys`.
- Added a new table `PinnedMessageReactions` and dao `PinnedMessageReactionDao` specifically for pinned messages.
- Updated `stream_chat`: `^2.2.0` -> `TODO`
## 2.2.0 ## 2.2.0
- Updated llc dependency - Updated llc dependency
@@ -11,14 +18,17 @@
## 2.1.0 ## 2.1.0
✅ Added ✅ Added
- Added support for `Message.i18n` - Added support for `Message.i18n`
- Added support for `User.language` - Added support for `User.language`
## 2.0.0 ## 2.0.0
* Migrate this package to null safety * Migrate this package to null safety
* Minor fixes and improvements * Minor fixes and improvements
## 2.0.0-nullsafety.8 ## 2.0.0-nullsafety.8
* Updated llc dependency * Updated llc dependency
* Upgraded moor dependencies and generated files with the latest dependency * Upgraded moor dependencies and generated files with the latest dependency
@@ -42,10 +42,9 @@ class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates all the channels using the new [channelList] data /// Updates all the channels using the new [channelList] data
Future<void> updateChannels(List<ChannelModel> channelList) => batch( Future<void> updateChannels(List<ChannelModel> channelList) => batch(
(it) => it.insertAll( (it) => it.insertAllOnConflictUpdate(
channels, channels,
channelList.map((c) => c.toEntity()).toList(), channelList.map((c) => c.toEntity()).toList(),
mode: InsertMode.insertOrReplace,
), ),
); );
} }
@@ -45,13 +45,12 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
} }
await batch((it) { await batch((it) {
it.insertAll( it.insertAllOnConflictUpdate(
channelQueries, channelQueries,
cids cids
.map((cid) => .map((cid) =>
ChannelQueryEntity(queryHash: hash, channelCid: cid)) ChannelQueryEntity(queryHash: hash, channelCid: cid))
.toList(), .toList(),
mode: InsertMode.insertOrReplace,
); );
}); });
}); });
@@ -26,7 +26,7 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
/// Update stored connection event with latest data /// Update stored connection event with latest data
Future<int> updateConnectionEvent(Event event) => transaction(() async { Future<int> updateConnectionEvent(Event event) => transaction(() async {
final connectionInfo = await select(connectionEvents).getSingleOrNull(); final connectionInfo = await select(connectionEvents).getSingleOrNull();
return into(connectionEvents).insert( return into(connectionEvents).insertOnConflictUpdate(
ConnectionEventEntity( ConnectionEventEntity(
id: 1, id: 1,
type: event.type, type: event.type,
@@ -38,7 +38,6 @@ class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
unreadChannels: unreadChannels:
event.unreadChannels ?? connectionInfo?.unreadChannels, event.unreadChannels ?? connectionInfo?.unreadChannels,
), ),
mode: InsertMode.insertOrReplace,
); );
}); });
@@ -4,6 +4,7 @@ export 'connection_event_dao.dart';
export 'member_dao.dart'; export 'member_dao.dart';
export 'message_dao.dart'; export 'message_dao.dart';
export 'pinned_message_dao.dart'; export 'pinned_message_dao.dart';
export 'pinned_message_reaction_dao.dart';
export 'reaction_dao.dart'; export 'reaction_dao.dart';
export 'read_dao.dart'; export 'read_dao.dart';
export 'user_dao.dart'; export 'user_dao.dart';
@@ -30,14 +30,19 @@ class MemberDao extends DatabaseAccessor<MoorChatDatabase>
}).get(); }).get();
/// Updates all the members using the new [memberList] data /// Updates all the members using the new [memberList] data
Future<void> updateMembers(String cid, List<Member> memberList) async => Future<void> updateMembers(String cid, List<Member> memberList) =>
batch( bulkUpdateMembers({cid: memberList});
(it) => it.insertAll(
members, /// Bulk updates the members data of multiple channels
memberList.map((m) => m.toEntity(cid: cid)).toList(), Future<void> bulkUpdateMembers(Map<String, List<Member>> channelWithMembers) {
mode: InsertMode.insertOrReplace, final entities = channelWithMembers.entries
), .map((entry) => entry.value.map(
); (member) => member.toEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch((batch) => batch.insertAllOnConflictUpdate(members, entities));
}
/// Deletes all the members whose [Members.channelCid] is present in [cids] /// Deletes all the members whose [Members.channelCid] is present in [cids]
Future<void> deleteMemberByCids(List<String> cids) async => batch((it) { Future<void> deleteMemberByCids(List<String> cids) async => batch((it) {
@@ -170,13 +170,21 @@ class MessageDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates the message data of a particular channel with /// Updates the message data of a particular channel with
/// the new [messageList] data /// the new [messageList] data
Future<void> updateMessages(String cid, List<Message> messageList) => batch( Future<void> updateMessages(String cid, List<Message> messageList) =>
(batch) { bulkUpdateMessages({cid: messageList});
batch.insertAll(
messages, /// Bulk updates the message data of multiple channels
messageList.map((it) => it.toEntity(cid: cid)).toList(), Future<void> bulkUpdateMessages(
mode: InsertMode.insertOrReplace, Map<String, List<Message>> channelWithMessages,
); ) {
}, final entities = channelWithMessages.entries
); .map((entry) => entry.value.map(
(message) => message.toEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch(
(batch) => batch.insertAllOnConflictUpdate(messages, entities),
);
}
} }
@@ -39,8 +39,10 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
final userEntity = rows.readTableOrNull(users); final userEntity = rows.readTableOrNull(users);
final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers); final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers);
final msgEntity = rows.readTable(pinnedMessages); final msgEntity = rows.readTable(pinnedMessages);
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id); final latestReactions =
final ownReactions = await _db.reactionDao.getReactionsByUserId( await _db.pinnedMessageReactionDao.getReactions(msgEntity.id);
final ownReactions =
await _db.pinnedMessageReactionDao.getReactionsByUserId(
msgEntity.id, msgEntity.id,
_db.userId, _db.userId,
); );
@@ -168,13 +170,21 @@ class PinnedMessageDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates the message data of a particular channel with /// Updates the message data of a particular channel with
/// the new [messageList] data /// the new [messageList] data
Future<void> updateMessages(String cid, List<Message> messageList) => batch( Future<void> updateMessages(String cid, List<Message> messageList) =>
(batch) { bulkUpdateMessages({cid: messageList});
batch.insertAll(
pinnedMessages, /// Bulk updates the message data of multiple channels
messageList.map((it) => it.toPinnedEntity(cid: cid)).toList(), Future<void> bulkUpdateMessages(
mode: InsertMode.insertOrReplace, Map<String, List<Message>> channelWithMessages,
); ) {
}, final entities = channelWithMessages.entries
); .map((entry) => entry.value.map(
(message) => message.toPinnedEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch(
(batch) => batch.insertAllOnConflictUpdate(pinnedMessages, entities),
);
}
} }
@@ -0,0 +1,60 @@
import 'package:moor/moor.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/entity/pinned_message_reactions.dart';
import 'package:stream_chat_persistence/src/entity/users.dart';
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
part 'pinned_message_reaction_dao.g.dart';
/// The Data Access Object for operations in [PinnedMessageReactions] table.
@UseDao(tables: [PinnedMessageReactions, Users])
class PinnedMessageReactionDao extends DatabaseAccessor<MoorChatDatabase>
with _$PinnedMessageReactionDaoMixin {
/// Creates a new reaction dao instance
PinnedMessageReactionDao(MoorChatDatabase db) : super(db);
/// Returns all the reactions of a particular message by matching
/// [Reactions.messageId] with [messageId]
Future<List<Reaction>> getReactions(String messageId) =>
(select(pinnedMessageReactions).join([
leftOuterJoin(users, pinnedMessageReactions.userId.equalsExp(users.id)),
])
..where(pinnedMessageReactions.messageId.equals(messageId))
..orderBy([OrderingTerm.asc(pinnedMessageReactions.createdAt)]))
.map((rows) {
final userEntity = rows.readTableOrNull(users);
final reactionEntity = rows.readTable(pinnedMessageReactions);
return reactionEntity.toReaction(user: userEntity?.toUser());
}).get();
/// Returns all the reactions of a particular message
/// added by a particular user by matching
/// [Reactions.messageId] with [messageId] and
/// [Reactions.userId] with [userId]
Future<List<Reaction>> getReactionsByUserId(
String messageId,
String userId,
) async {
final reactions = await getReactions(messageId);
return reactions.where((it) => it.userId == userId).toList();
}
/// Updates the reactions data with the new [reactionList] data
Future<void> updateReactions(List<Reaction> reactionList) => batch((it) {
it.insertAllOnConflictUpdate(
pinnedMessageReactions,
reactionList.map((r) => r.toPinnedEntity()).toList(),
);
});
/// Deletes all the reactions whose [Reactions.messageId] is
/// present in [messageIds]
Future<void> deleteReactionsByMessageIds(List<String> messageIds) =>
batch((it) {
it.deleteWhere<PinnedMessageReactions, PinnedMessageReactionEntity>(
pinnedMessageReactions,
(r) => r.messageId.isIn(messageIds),
);
});
}
@@ -0,0 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'pinned_message_reaction_dao.dart';
// **************************************************************************
// DaoGenerator
// **************************************************************************
mixin _$PinnedMessageReactionDaoMixin on DatabaseAccessor<MoorChatDatabase> {
$PinnedMessageReactionsTable get pinnedMessageReactions =>
attachedDatabase.pinnedMessageReactions;
$UsersTable get users => attachedDatabase.users;
}
@@ -42,10 +42,9 @@ class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
/// Updates the reactions data with the new [reactionList] data /// Updates the reactions data with the new [reactionList] data
Future<void> updateReactions(List<Reaction> reactionList) => batch((it) { Future<void> updateReactions(List<Reaction> reactionList) => batch((it) {
it.insertAll( it.insertAllOnConflictUpdate(
reactions, reactions,
reactionList.map((r) => r.toEntity()).toList(), reactionList.map((r) => r.toEntity()).toList(),
mode: InsertMode.insertOrReplace,
); );
}); });
@@ -29,11 +29,17 @@ class ReadDao extends DatabaseAccessor<MoorChatDatabase> with _$ReadDaoMixin {
/// Updates the read data of a particular channel with /// Updates the read data of a particular channel with
/// the new [readList] data /// the new [readList] data
Future<void> updateReads(String cid, List<Read> readList) => batch( Future<void> updateReads(String cid, List<Read> readList) =>
(it) => it.insertAll( bulkUpdateReads({cid: readList});
reads,
readList.map((r) => r.toEntity(cid: cid)).toList(), /// Bulk updates the reads data of multiple channels
mode: InsertMode.insertOrReplace, Future<void> bulkUpdateReads(Map<String, List<Read>> channelWithReads) {
), final entities = channelWithReads.entries
); .map((entry) => entry.value.map(
(read) => read.toEntity(cid: entry.key),
))
.expand((it) => it)
.toList(growable: false);
return batch((batch) => batch.insertAllOnConflictUpdate(reads, entities));
}
} }
@@ -14,10 +14,9 @@ class UserDao extends DatabaseAccessor<MoorChatDatabase> with _$UserDaoMixin {
/// Updates the users data with the new [userList] data /// Updates the users data with the new [userList] data
Future<void> updateUsers(List<User> userList) => batch( Future<void> updateUsers(List<User> userList) => batch(
(it) => it.insertAll( (it) => it.insertAllOnConflictUpdate(
users, users,
userList.map((u) => u.toEntity()).toList(), userList.map((u) => u.toEntity()).toList(),
mode: InsertMode.insertOrReplace,
), ),
); );
@@ -14,6 +14,7 @@ part 'moor_chat_database.g.dart';
Channels, Channels,
Messages, Messages,
PinnedMessages, PinnedMessages,
PinnedMessageReactions,
Reactions, Reactions,
Users, Users,
Members, Members,
@@ -25,6 +26,7 @@ part 'moor_chat_database.g.dart';
ChannelDao, ChannelDao,
MessageDao, MessageDao,
PinnedMessageDao, PinnedMessageDao,
PinnedMessageReactionDao,
MemberDao, MemberDao,
ReactionDao, ReactionDao,
ReadDao, ReadDao,
@@ -51,10 +53,13 @@ class MoorChatDatabase extends _$MoorChatDatabase {
// you should bump this number whenever you change or add a table definition. // you should bump this number whenever you change or add a table definition.
@override @override
int get schemaVersion => 5; int get schemaVersion => 6;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
},
onUpgrade: (openingDetails, before, after) async { onUpgrade: (openingDetails, before, after) async {
if (before != after) { if (before != after) {
final m = createMigrator(); final m = createMigrator();
@@ -645,7 +645,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
final String? pinnedByUserId; final String? pinnedByUserId;
/// The channel cid of which this message is part of /// The channel cid of which this message is part of
final String? channelCid; final String channelCid;
/// A Map of [messageText] translations. /// A Map of [messageText] translations.
final Map<String, String>? i18n; final Map<String, String>? i18n;
@@ -675,7 +675,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
this.pinnedAt, this.pinnedAt,
this.pinExpires, this.pinExpires,
this.pinnedByUserId, this.pinnedByUserId,
this.channelCid, required this.channelCid,
this.i18n, this.i18n,
this.extraData}); this.extraData});
factory MessageEntity.fromData( factory MessageEntity.fromData(
@@ -728,7 +728,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
pinnedByUserId: const StringType() pinnedByUserId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']),
channelCid: const StringType() channelCid: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!,
i18n: $MessagesTable.$converter5.mapToDart(const StringType() i18n: $MessagesTable.$converter5.mapToDart(const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}i18n'])), .mapFromDatabaseResponse(data['${effectivePrefix}i18n'])),
extraData: $MessagesTable.$converter6.mapToDart(const StringType() extraData: $MessagesTable.$converter6.mapToDart(const StringType()
@@ -800,9 +800,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
if (!nullToAbsent || pinnedByUserId != null) { if (!nullToAbsent || pinnedByUserId != null) {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId); map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId);
} }
if (!nullToAbsent || channelCid != null) { map['channel_cid'] = Variable<String>(channelCid);
map['channel_cid'] = Variable<String?>(channelCid);
}
if (!nullToAbsent || i18n != null) { if (!nullToAbsent || i18n != null) {
final converter = $MessagesTable.$converter5; final converter = $MessagesTable.$converter5;
map['i18n'] = Variable<String?>(converter.mapToSql(i18n)); map['i18n'] = Variable<String?>(converter.mapToSql(i18n));
@@ -842,7 +840,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
pinnedAt: serializer.fromJson<DateTime?>(json['pinnedAt']), pinnedAt: serializer.fromJson<DateTime?>(json['pinnedAt']),
pinExpires: serializer.fromJson<DateTime?>(json['pinExpires']), pinExpires: serializer.fromJson<DateTime?>(json['pinExpires']),
pinnedByUserId: serializer.fromJson<String?>(json['pinnedByUserId']), pinnedByUserId: serializer.fromJson<String?>(json['pinnedByUserId']),
channelCid: serializer.fromJson<String?>(json['channelCid']), channelCid: serializer.fromJson<String>(json['channelCid']),
i18n: serializer.fromJson<Map<String, String>?>(json['i18n']), i18n: serializer.fromJson<Map<String, String>?>(json['i18n']),
extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']), extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']),
); );
@@ -873,7 +871,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
'pinnedAt': serializer.toJson<DateTime?>(pinnedAt), 'pinnedAt': serializer.toJson<DateTime?>(pinnedAt),
'pinExpires': serializer.toJson<DateTime?>(pinExpires), 'pinExpires': serializer.toJson<DateTime?>(pinExpires),
'pinnedByUserId': serializer.toJson<String?>(pinnedByUserId), 'pinnedByUserId': serializer.toJson<String?>(pinnedByUserId),
'channelCid': serializer.toJson<String?>(channelCid), 'channelCid': serializer.toJson<String>(channelCid),
'i18n': serializer.toJson<Map<String, String>?>(i18n), 'i18n': serializer.toJson<Map<String, String>?>(i18n),
'extraData': serializer.toJson<Map<String, Object?>?>(extraData), 'extraData': serializer.toJson<Map<String, Object?>?>(extraData),
}; };
@@ -902,7 +900,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
Value<DateTime?> pinnedAt = const Value.absent(), Value<DateTime?> pinnedAt = const Value.absent(),
Value<DateTime?> pinExpires = const Value.absent(), Value<DateTime?> pinExpires = const Value.absent(),
Value<String?> pinnedByUserId = const Value.absent(), Value<String?> pinnedByUserId = const Value.absent(),
Value<String?> channelCid = const Value.absent(), String? channelCid,
Value<Map<String, String>?> i18n = const Value.absent(), Value<Map<String, String>?> i18n = const Value.absent(),
Value<Map<String, Object?>?> extraData = const Value.absent()}) => Value<Map<String, Object?>?> extraData = const Value.absent()}) =>
MessageEntity( MessageEntity(
@@ -934,7 +932,7 @@ class MessageEntity extends DataClass implements Insertable<MessageEntity> {
pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires,
pinnedByUserId: pinnedByUserId:
pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId,
channelCid: channelCid.present ? channelCid.value : this.channelCid, channelCid: channelCid ?? this.channelCid,
i18n: i18n.present ? i18n.value : this.i18n, i18n: i18n.present ? i18n.value : this.i18n,
extraData: extraData.present ? extraData.value : this.extraData, extraData: extraData.present ? extraData.value : this.extraData,
); );
@@ -1068,7 +1066,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
final Value<DateTime?> pinnedAt; final Value<DateTime?> pinnedAt;
final Value<DateTime?> pinExpires; final Value<DateTime?> pinExpires;
final Value<String?> pinnedByUserId; final Value<String?> pinnedByUserId;
final Value<String?> channelCid; final Value<String> channelCid;
final Value<Map<String, String>?> i18n; final Value<Map<String, String>?> i18n;
final Value<Map<String, Object?>?> extraData; final Value<Map<String, Object?>?> extraData;
const MessagesCompanion({ const MessagesCompanion({
@@ -1121,12 +1119,13 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
this.pinnedAt = const Value.absent(), this.pinnedAt = const Value.absent(),
this.pinExpires = const Value.absent(), this.pinExpires = const Value.absent(),
this.pinnedByUserId = const Value.absent(), this.pinnedByUserId = const Value.absent(),
this.channelCid = const Value.absent(), required String channelCid,
this.i18n = const Value.absent(), this.i18n = const Value.absent(),
this.extraData = const Value.absent(), this.extraData = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
attachments = Value(attachments), attachments = Value(attachments),
mentionedUsers = Value(mentionedUsers); mentionedUsers = Value(mentionedUsers),
channelCid = Value(channelCid);
static Insertable<MessageEntity> custom({ static Insertable<MessageEntity> custom({
Expression<String>? id, Expression<String>? id,
Expression<String?>? messageText, Expression<String?>? messageText,
@@ -1150,7 +1149,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
Expression<DateTime?>? pinnedAt, Expression<DateTime?>? pinnedAt,
Expression<DateTime?>? pinExpires, Expression<DateTime?>? pinExpires,
Expression<String?>? pinnedByUserId, Expression<String?>? pinnedByUserId,
Expression<String?>? channelCid, Expression<String>? channelCid,
Expression<Map<String, String>?>? i18n, Expression<Map<String, String>?>? i18n,
Expression<Map<String, Object?>?>? extraData, Expression<Map<String, Object?>?>? extraData,
}) { }) {
@@ -1206,7 +1205,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
Value<DateTime?>? pinnedAt, Value<DateTime?>? pinnedAt,
Value<DateTime?>? pinExpires, Value<DateTime?>? pinExpires,
Value<String?>? pinnedByUserId, Value<String?>? pinnedByUserId,
Value<String?>? channelCid, Value<String>? channelCid,
Value<Map<String, String>?>? i18n, Value<Map<String, String>?>? i18n,
Value<Map<String, Object?>?>? extraData}) { Value<Map<String, Object?>?>? extraData}) {
return MessagesCompanion( return MessagesCompanion(
@@ -1317,7 +1316,7 @@ class MessagesCompanion extends UpdateCompanion<MessageEntity> {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId.value); map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId.value);
} }
if (channelCid.present) { if (channelCid.present) {
map['channel_cid'] = Variable<String?>(channelCid.value); map['channel_cid'] = Variable<String>(channelCid.value);
} }
if (i18n.present) { if (i18n.present) {
final converter = $MessagesTable.$converter5; final converter = $MessagesTable.$converter5;
@@ -1491,11 +1490,10 @@ class $MessagesTable extends Messages
typeName: 'TEXT', requiredDuringInsert: false); typeName: 'TEXT', requiredDuringInsert: false);
final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid');
late final GeneratedColumn<String?> channelCid = GeneratedColumn<String?>( late final GeneratedColumn<String?> channelCid = GeneratedColumn<String?>(
'channel_cid', aliasedName, true, 'channel_cid', aliasedName, false,
typeName: 'TEXT', typeName: 'TEXT',
requiredDuringInsert: false, requiredDuringInsert: true,
$customConstraints: $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE');
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE');
final VerificationMeta _i18nMeta = const VerificationMeta('i18n'); final VerificationMeta _i18nMeta = const VerificationMeta('i18n');
late final GeneratedColumnWithTypeConverter<Map<String, String>, String?> late final GeneratedColumnWithTypeConverter<Map<String, String>, String?>
i18n = GeneratedColumn<String?>('i18n', aliasedName, true, i18n = GeneratedColumn<String?>('i18n', aliasedName, true,
@@ -1634,6 +1632,8 @@ class $MessagesTable extends Messages
_channelCidMeta, _channelCidMeta,
channelCid.isAcceptableOrUnknown( channelCid.isAcceptableOrUnknown(
data['channel_cid']!, _channelCidMeta)); data['channel_cid']!, _channelCidMeta));
} else if (isInserting) {
context.missing(_channelCidMeta);
} }
context.handle(_i18nMeta, const VerificationResult.success()); context.handle(_i18nMeta, const VerificationResult.success());
context.handle(_extraDataMeta, const VerificationResult.success()); context.handle(_extraDataMeta, const VerificationResult.success());
@@ -1739,7 +1739,7 @@ class PinnedMessageEntity extends DataClass
final String? pinnedByUserId; final String? pinnedByUserId;
/// The channel cid of which this message is part of /// The channel cid of which this message is part of
final String? channelCid; final String channelCid;
/// A Map of [messageText] translations. /// A Map of [messageText] translations.
final Map<String, String>? i18n; final Map<String, String>? i18n;
@@ -1769,7 +1769,7 @@ class PinnedMessageEntity extends DataClass
this.pinnedAt, this.pinnedAt,
this.pinExpires, this.pinExpires,
this.pinnedByUserId, this.pinnedByUserId,
this.channelCid, required this.channelCid,
this.i18n, this.i18n,
this.extraData}); this.extraData});
factory PinnedMessageEntity.fromData( factory PinnedMessageEntity.fromData(
@@ -1825,7 +1825,7 @@ class PinnedMessageEntity extends DataClass
pinnedByUserId: const StringType() pinnedByUserId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']), .mapFromDatabaseResponse(data['${effectivePrefix}pinned_by_user_id']),
channelCid: const StringType() channelCid: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}channel_cid']), .mapFromDatabaseResponse(data['${effectivePrefix}channel_cid'])!,
i18n: $PinnedMessagesTable.$converter5.mapToDart(const StringType() i18n: $PinnedMessagesTable.$converter5.mapToDart(const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}i18n'])), .mapFromDatabaseResponse(data['${effectivePrefix}i18n'])),
extraData: $PinnedMessagesTable.$converter6.mapToDart(const StringType() extraData: $PinnedMessagesTable.$converter6.mapToDart(const StringType()
@@ -1897,9 +1897,7 @@ class PinnedMessageEntity extends DataClass
if (!nullToAbsent || pinnedByUserId != null) { if (!nullToAbsent || pinnedByUserId != null) {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId); map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId);
} }
if (!nullToAbsent || channelCid != null) { map['channel_cid'] = Variable<String>(channelCid);
map['channel_cid'] = Variable<String?>(channelCid);
}
if (!nullToAbsent || i18n != null) { if (!nullToAbsent || i18n != null) {
final converter = $PinnedMessagesTable.$converter5; final converter = $PinnedMessagesTable.$converter5;
map['i18n'] = Variable<String?>(converter.mapToSql(i18n)); map['i18n'] = Variable<String?>(converter.mapToSql(i18n));
@@ -1939,7 +1937,7 @@ class PinnedMessageEntity extends DataClass
pinnedAt: serializer.fromJson<DateTime?>(json['pinnedAt']), pinnedAt: serializer.fromJson<DateTime?>(json['pinnedAt']),
pinExpires: serializer.fromJson<DateTime?>(json['pinExpires']), pinExpires: serializer.fromJson<DateTime?>(json['pinExpires']),
pinnedByUserId: serializer.fromJson<String?>(json['pinnedByUserId']), pinnedByUserId: serializer.fromJson<String?>(json['pinnedByUserId']),
channelCid: serializer.fromJson<String?>(json['channelCid']), channelCid: serializer.fromJson<String>(json['channelCid']),
i18n: serializer.fromJson<Map<String, String>?>(json['i18n']), i18n: serializer.fromJson<Map<String, String>?>(json['i18n']),
extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']), extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']),
); );
@@ -1970,7 +1968,7 @@ class PinnedMessageEntity extends DataClass
'pinnedAt': serializer.toJson<DateTime?>(pinnedAt), 'pinnedAt': serializer.toJson<DateTime?>(pinnedAt),
'pinExpires': serializer.toJson<DateTime?>(pinExpires), 'pinExpires': serializer.toJson<DateTime?>(pinExpires),
'pinnedByUserId': serializer.toJson<String?>(pinnedByUserId), 'pinnedByUserId': serializer.toJson<String?>(pinnedByUserId),
'channelCid': serializer.toJson<String?>(channelCid), 'channelCid': serializer.toJson<String>(channelCid),
'i18n': serializer.toJson<Map<String, String>?>(i18n), 'i18n': serializer.toJson<Map<String, String>?>(i18n),
'extraData': serializer.toJson<Map<String, Object?>?>(extraData), 'extraData': serializer.toJson<Map<String, Object?>?>(extraData),
}; };
@@ -1999,7 +1997,7 @@ class PinnedMessageEntity extends DataClass
Value<DateTime?> pinnedAt = const Value.absent(), Value<DateTime?> pinnedAt = const Value.absent(),
Value<DateTime?> pinExpires = const Value.absent(), Value<DateTime?> pinExpires = const Value.absent(),
Value<String?> pinnedByUserId = const Value.absent(), Value<String?> pinnedByUserId = const Value.absent(),
Value<String?> channelCid = const Value.absent(), String? channelCid,
Value<Map<String, String>?> i18n = const Value.absent(), Value<Map<String, String>?> i18n = const Value.absent(),
Value<Map<String, Object?>?> extraData = const Value.absent()}) => Value<Map<String, Object?>?> extraData = const Value.absent()}) =>
PinnedMessageEntity( PinnedMessageEntity(
@@ -2031,7 +2029,7 @@ class PinnedMessageEntity extends DataClass
pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires,
pinnedByUserId: pinnedByUserId:
pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId,
channelCid: channelCid.present ? channelCid.value : this.channelCid, channelCid: channelCid ?? this.channelCid,
i18n: i18n.present ? i18n.value : this.i18n, i18n: i18n.present ? i18n.value : this.i18n,
extraData: extraData.present ? extraData.value : this.extraData, extraData: extraData.present ? extraData.value : this.extraData,
); );
@@ -2165,7 +2163,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
final Value<DateTime?> pinnedAt; final Value<DateTime?> pinnedAt;
final Value<DateTime?> pinExpires; final Value<DateTime?> pinExpires;
final Value<String?> pinnedByUserId; final Value<String?> pinnedByUserId;
final Value<String?> channelCid; final Value<String> channelCid;
final Value<Map<String, String>?> i18n; final Value<Map<String, String>?> i18n;
final Value<Map<String, Object?>?> extraData; final Value<Map<String, Object?>?> extraData;
const PinnedMessagesCompanion({ const PinnedMessagesCompanion({
@@ -2218,12 +2216,13 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
this.pinnedAt = const Value.absent(), this.pinnedAt = const Value.absent(),
this.pinExpires = const Value.absent(), this.pinExpires = const Value.absent(),
this.pinnedByUserId = const Value.absent(), this.pinnedByUserId = const Value.absent(),
this.channelCid = const Value.absent(), required String channelCid,
this.i18n = const Value.absent(), this.i18n = const Value.absent(),
this.extraData = const Value.absent(), this.extraData = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
attachments = Value(attachments), attachments = Value(attachments),
mentionedUsers = Value(mentionedUsers); mentionedUsers = Value(mentionedUsers),
channelCid = Value(channelCid);
static Insertable<PinnedMessageEntity> custom({ static Insertable<PinnedMessageEntity> custom({
Expression<String>? id, Expression<String>? id,
Expression<String?>? messageText, Expression<String?>? messageText,
@@ -2247,7 +2246,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
Expression<DateTime?>? pinnedAt, Expression<DateTime?>? pinnedAt,
Expression<DateTime?>? pinExpires, Expression<DateTime?>? pinExpires,
Expression<String?>? pinnedByUserId, Expression<String?>? pinnedByUserId,
Expression<String?>? channelCid, Expression<String>? channelCid,
Expression<Map<String, String>?>? i18n, Expression<Map<String, String>?>? i18n,
Expression<Map<String, Object?>?>? extraData, Expression<Map<String, Object?>?>? extraData,
}) { }) {
@@ -2303,7 +2302,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
Value<DateTime?>? pinnedAt, Value<DateTime?>? pinnedAt,
Value<DateTime?>? pinExpires, Value<DateTime?>? pinExpires,
Value<String?>? pinnedByUserId, Value<String?>? pinnedByUserId,
Value<String?>? channelCid, Value<String>? channelCid,
Value<Map<String, String>?>? i18n, Value<Map<String, String>?>? i18n,
Value<Map<String, Object?>?>? extraData}) { Value<Map<String, Object?>?>? extraData}) {
return PinnedMessagesCompanion( return PinnedMessagesCompanion(
@@ -2414,7 +2413,7 @@ class PinnedMessagesCompanion extends UpdateCompanion<PinnedMessageEntity> {
map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId.value); map['pinned_by_user_id'] = Variable<String?>(pinnedByUserId.value);
} }
if (channelCid.present) { if (channelCid.present) {
map['channel_cid'] = Variable<String?>(channelCid.value); map['channel_cid'] = Variable<String>(channelCid.value);
} }
if (i18n.present) { if (i18n.present) {
final converter = $PinnedMessagesTable.$converter5; final converter = $PinnedMessagesTable.$converter5;
@@ -2589,11 +2588,10 @@ class $PinnedMessagesTable extends PinnedMessages
typeName: 'TEXT', requiredDuringInsert: false); typeName: 'TEXT', requiredDuringInsert: false);
final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); final VerificationMeta _channelCidMeta = const VerificationMeta('channelCid');
late final GeneratedColumn<String?> channelCid = GeneratedColumn<String?>( late final GeneratedColumn<String?> channelCid = GeneratedColumn<String?>(
'channel_cid', aliasedName, true, 'channel_cid', aliasedName, false,
typeName: 'TEXT', typeName: 'TEXT',
requiredDuringInsert: false, requiredDuringInsert: true,
$customConstraints: $customConstraints: 'REFERENCES channels(cid) ON DELETE CASCADE');
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE');
final VerificationMeta _i18nMeta = const VerificationMeta('i18n'); final VerificationMeta _i18nMeta = const VerificationMeta('i18n');
late final GeneratedColumnWithTypeConverter<Map<String, String>, String?> late final GeneratedColumnWithTypeConverter<Map<String, String>, String?>
i18n = GeneratedColumn<String?>('i18n', aliasedName, true, i18n = GeneratedColumn<String?>('i18n', aliasedName, true,
@@ -2734,6 +2732,8 @@ class $PinnedMessagesTable extends PinnedMessages
_channelCidMeta, _channelCidMeta,
channelCid.isAcceptableOrUnknown( channelCid.isAcceptableOrUnknown(
data['channel_cid']!, _channelCidMeta)); data['channel_cid']!, _channelCidMeta));
} else if (isInserting) {
context.missing(_channelCidMeta);
} }
context.handle(_i18nMeta, const VerificationResult.success()); context.handle(_i18nMeta, const VerificationResult.success());
context.handle(_extraDataMeta, const VerificationResult.success()); context.handle(_extraDataMeta, const VerificationResult.success());
@@ -2769,6 +2769,340 @@ class $PinnedMessagesTable extends PinnedMessages
MapConverter<Object?>(); MapConverter<Object?>();
} }
class PinnedMessageReactionEntity extends DataClass
implements Insertable<PinnedMessageReactionEntity> {
/// The id of the user that sent the reaction
final String userId;
/// The messageId to which the reaction belongs
final String messageId;
/// The type of the reaction
final String type;
/// The DateTime on which the reaction is created
final DateTime createdAt;
/// The score of the reaction (ie. number of reactions sent)
final int score;
/// Reaction custom extraData
final Map<String, Object?>? extraData;
PinnedMessageReactionEntity(
{required this.userId,
required this.messageId,
required this.type,
required this.createdAt,
required this.score,
this.extraData});
factory PinnedMessageReactionEntity.fromData(
Map<String, dynamic> data, GeneratedDatabase db,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return PinnedMessageReactionEntity(
userId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}user_id'])!,
messageId: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}message_id'])!,
type: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}type'])!,
createdAt: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}created_at'])!,
score: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}score'])!,
extraData: $PinnedMessageReactionsTable.$converter0.mapToDart(
const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}extra_data'])),
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['user_id'] = Variable<String>(userId);
map['message_id'] = Variable<String>(messageId);
map['type'] = Variable<String>(type);
map['created_at'] = Variable<DateTime>(createdAt);
map['score'] = Variable<int>(score);
if (!nullToAbsent || extraData != null) {
final converter = $PinnedMessageReactionsTable.$converter0;
map['extra_data'] = Variable<String?>(converter.mapToSql(extraData));
}
return map;
}
factory PinnedMessageReactionEntity.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return PinnedMessageReactionEntity(
userId: serializer.fromJson<String>(json['userId']),
messageId: serializer.fromJson<String>(json['messageId']),
type: serializer.fromJson<String>(json['type']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
score: serializer.fromJson<int>(json['score']),
extraData: serializer.fromJson<Map<String, Object?>?>(json['extraData']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'userId': serializer.toJson<String>(userId),
'messageId': serializer.toJson<String>(messageId),
'type': serializer.toJson<String>(type),
'createdAt': serializer.toJson<DateTime>(createdAt),
'score': serializer.toJson<int>(score),
'extraData': serializer.toJson<Map<String, Object?>?>(extraData),
};
}
PinnedMessageReactionEntity copyWith(
{String? userId,
String? messageId,
String? type,
DateTime? createdAt,
int? score,
Value<Map<String, Object?>?> extraData = const Value.absent()}) =>
PinnedMessageReactionEntity(
userId: userId ?? this.userId,
messageId: messageId ?? this.messageId,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
score: score ?? this.score,
extraData: extraData.present ? extraData.value : this.extraData,
);
@override
String toString() {
return (StringBuffer('PinnedMessageReactionEntity(')
..write('userId: $userId, ')
..write('messageId: $messageId, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('score: $score, ')
..write('extraData: $extraData')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(
userId.hashCode,
$mrjc(
messageId.hashCode,
$mrjc(
type.hashCode,
$mrjc(createdAt.hashCode,
$mrjc(score.hashCode, extraData.hashCode))))));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PinnedMessageReactionEntity &&
other.userId == this.userId &&
other.messageId == this.messageId &&
other.type == this.type &&
other.createdAt == this.createdAt &&
other.score == this.score &&
other.extraData == this.extraData);
}
class PinnedMessageReactionsCompanion
extends UpdateCompanion<PinnedMessageReactionEntity> {
final Value<String> userId;
final Value<String> messageId;
final Value<String> type;
final Value<DateTime> createdAt;
final Value<int> score;
final Value<Map<String, Object?>?> extraData;
const PinnedMessageReactionsCompanion({
this.userId = const Value.absent(),
this.messageId = const Value.absent(),
this.type = const Value.absent(),
this.createdAt = const Value.absent(),
this.score = const Value.absent(),
this.extraData = const Value.absent(),
});
PinnedMessageReactionsCompanion.insert({
required String userId,
required String messageId,
required String type,
this.createdAt = const Value.absent(),
this.score = const Value.absent(),
this.extraData = const Value.absent(),
}) : userId = Value(userId),
messageId = Value(messageId),
type = Value(type);
static Insertable<PinnedMessageReactionEntity> custom({
Expression<String>? userId,
Expression<String>? messageId,
Expression<String>? type,
Expression<DateTime>? createdAt,
Expression<int>? score,
Expression<Map<String, Object?>?>? extraData,
}) {
return RawValuesInsertable({
if (userId != null) 'user_id': userId,
if (messageId != null) 'message_id': messageId,
if (type != null) 'type': type,
if (createdAt != null) 'created_at': createdAt,
if (score != null) 'score': score,
if (extraData != null) 'extra_data': extraData,
});
}
PinnedMessageReactionsCompanion copyWith(
{Value<String>? userId,
Value<String>? messageId,
Value<String>? type,
Value<DateTime>? createdAt,
Value<int>? score,
Value<Map<String, Object?>?>? extraData}) {
return PinnedMessageReactionsCompanion(
userId: userId ?? this.userId,
messageId: messageId ?? this.messageId,
type: type ?? this.type,
createdAt: createdAt ?? this.createdAt,
score: score ?? this.score,
extraData: extraData ?? this.extraData,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (userId.present) {
map['user_id'] = Variable<String>(userId.value);
}
if (messageId.present) {
map['message_id'] = Variable<String>(messageId.value);
}
if (type.present) {
map['type'] = Variable<String>(type.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
if (score.present) {
map['score'] = Variable<int>(score.value);
}
if (extraData.present) {
final converter = $PinnedMessageReactionsTable.$converter0;
map['extra_data'] =
Variable<String?>(converter.mapToSql(extraData.value));
}
return map;
}
@override
String toString() {
return (StringBuffer('PinnedMessageReactionsCompanion(')
..write('userId: $userId, ')
..write('messageId: $messageId, ')
..write('type: $type, ')
..write('createdAt: $createdAt, ')
..write('score: $score, ')
..write('extraData: $extraData')
..write(')'))
.toString();
}
}
class $PinnedMessageReactionsTable extends PinnedMessageReactions
with TableInfo<$PinnedMessageReactionsTable, PinnedMessageReactionEntity> {
final GeneratedDatabase _db;
final String? _alias;
$PinnedMessageReactionsTable(this._db, [this._alias]);
final VerificationMeta _userIdMeta = const VerificationMeta('userId');
late final GeneratedColumn<String?> userId = GeneratedColumn<String?>(
'user_id', aliasedName, false,
typeName: 'TEXT', requiredDuringInsert: true);
final VerificationMeta _messageIdMeta = const VerificationMeta('messageId');
late final GeneratedColumn<String?> messageId = GeneratedColumn<String?>(
'message_id', aliasedName, false,
typeName: 'TEXT',
requiredDuringInsert: true,
$customConstraints: 'REFERENCES pinned_messages(id) ON DELETE CASCADE');
final VerificationMeta _typeMeta = const VerificationMeta('type');
late final GeneratedColumn<String?> type = GeneratedColumn<String?>(
'type', aliasedName, false,
typeName: 'TEXT', requiredDuringInsert: true);
final VerificationMeta _createdAtMeta = const VerificationMeta('createdAt');
late final GeneratedColumn<DateTime?> createdAt = GeneratedColumn<DateTime?>(
'created_at', aliasedName, false,
typeName: 'INTEGER',
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
final VerificationMeta _scoreMeta = const VerificationMeta('score');
late final GeneratedColumn<int?> score = GeneratedColumn<int?>(
'score', aliasedName, false,
typeName: 'INTEGER',
requiredDuringInsert: false,
defaultValue: const Constant(0));
final VerificationMeta _extraDataMeta = const VerificationMeta('extraData');
late final GeneratedColumnWithTypeConverter<Map<String, Object?>, String?>
extraData = GeneratedColumn<String?>('extra_data', aliasedName, true,
typeName: 'TEXT', requiredDuringInsert: false)
.withConverter<Map<String, Object?>>(
$PinnedMessageReactionsTable.$converter0);
@override
List<GeneratedColumn> get $columns =>
[userId, messageId, type, createdAt, score, extraData];
@override
String get aliasedName => _alias ?? 'pinned_message_reactions';
@override
String get actualTableName => 'pinned_message_reactions';
@override
VerificationContext validateIntegrity(
Insertable<PinnedMessageReactionEntity> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('user_id')) {
context.handle(_userIdMeta,
userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta));
} else if (isInserting) {
context.missing(_userIdMeta);
}
if (data.containsKey('message_id')) {
context.handle(_messageIdMeta,
messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta));
} else if (isInserting) {
context.missing(_messageIdMeta);
}
if (data.containsKey('type')) {
context.handle(
_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta));
} else if (isInserting) {
context.missing(_typeMeta);
}
if (data.containsKey('created_at')) {
context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
}
if (data.containsKey('score')) {
context.handle(
_scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta));
}
context.handle(_extraDataMeta, const VerificationResult.success());
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {messageId, type, userId};
@override
PinnedMessageReactionEntity map(Map<String, dynamic> data,
{String? tablePrefix}) {
return PinnedMessageReactionEntity.fromData(data, _db,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$PinnedMessageReactionsTable createAlias(String alias) {
return $PinnedMessageReactionsTable(_db, alias);
}
static TypeConverter<Map<String, Object?>, String> $converter0 =
MapConverter<Object?>();
}
class ReactionEntity extends DataClass implements Insertable<ReactionEntity> { class ReactionEntity extends DataClass implements Insertable<ReactionEntity> {
/// The id of the user that sent the reaction /// The id of the user that sent the reaction
final String userId; final String userId;
@@ -4897,6 +5231,8 @@ abstract class _$MoorChatDatabase extends GeneratedDatabase {
late final $ChannelsTable channels = $ChannelsTable(this); late final $ChannelsTable channels = $ChannelsTable(this);
late final $MessagesTable messages = $MessagesTable(this); late final $MessagesTable messages = $MessagesTable(this);
late final $PinnedMessagesTable pinnedMessages = $PinnedMessagesTable(this); late final $PinnedMessagesTable pinnedMessages = $PinnedMessagesTable(this);
late final $PinnedMessageReactionsTable pinnedMessageReactions =
$PinnedMessageReactionsTable(this);
late final $ReactionsTable reactions = $ReactionsTable(this); late final $ReactionsTable reactions = $ReactionsTable(this);
late final $UsersTable users = $UsersTable(this); late final $UsersTable users = $UsersTable(this);
late final $MembersTable members = $MembersTable(this); late final $MembersTable members = $MembersTable(this);
@@ -4909,6 +5245,8 @@ abstract class _$MoorChatDatabase extends GeneratedDatabase {
late final MessageDao messageDao = MessageDao(this as MoorChatDatabase); late final MessageDao messageDao = MessageDao(this as MoorChatDatabase);
late final PinnedMessageDao pinnedMessageDao = late final PinnedMessageDao pinnedMessageDao =
PinnedMessageDao(this as MoorChatDatabase); PinnedMessageDao(this as MoorChatDatabase);
late final PinnedMessageReactionDao pinnedMessageReactionDao =
PinnedMessageReactionDao(this as MoorChatDatabase);
late final MemberDao memberDao = MemberDao(this as MoorChatDatabase); late final MemberDao memberDao = MemberDao(this as MoorChatDatabase);
late final ReactionDao reactionDao = ReactionDao(this as MoorChatDatabase); late final ReactionDao reactionDao = ReactionDao(this as MoorChatDatabase);
late final ReadDao readDao = ReadDao(this as MoorChatDatabase); late final ReadDao readDao = ReadDao(this as MoorChatDatabase);
@@ -4923,6 +5261,7 @@ abstract class _$MoorChatDatabase extends GeneratedDatabase {
channels, channels,
messages, messages,
pinnedMessages, pinnedMessages,
pinnedMessageReactions,
reactions, reactions,
users, users,
members, members,
@@ -3,6 +3,7 @@ export 'channels.dart';
export 'connection_events.dart'; export 'connection_events.dart';
export 'members.dart'; export 'members.dart';
export 'messages.dart'; export 'messages.dart';
export 'pinned_message_reactions.dart';
export 'pinned_messages.dart'; export 'pinned_messages.dart';
export 'reactions.dart'; export 'reactions.dart';
export 'reads.dart'; export 'reads.dart';
@@ -39,8 +39,5 @@ class Members extends Table {
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
@override @override
Set<Column> get primaryKey => { Set<Column> get primaryKey => {userId, channelCid};
userId,
channelCid,
};
} }
@@ -77,8 +77,8 @@ class Messages extends Table {
TextColumn get pinnedByUserId => text().nullable()(); TextColumn get pinnedByUserId => text().nullable()();
/// The channel cid of which this message is part of /// The channel cid of which this message is part of
TextColumn get channelCid => text().nullable().customConstraint( TextColumn get channelCid =>
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE')(); text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')();
/// A Map of [messageText] translations. /// A Map of [messageText] translations.
TextColumn get i18n => text().nullable().map(MapConverter<String>())(); TextColumn get i18n => text().nullable().map(MapConverter<String>())();
@@ -0,0 +1,13 @@
// coverage:ignore-file
import 'package:moor/moor.dart';
import 'package:stream_chat_persistence/src/entity/reactions.dart';
/// Represents a [PinnedMessageReactions] table in [MoorChatDatabase].
@DataClassName('PinnedMessageReactionEntity')
class PinnedMessageReactions extends Reactions {
/// The messageId to which the reaction belongs
@override
TextColumn get messageId => text()
.customConstraint('REFERENCES pinned_messages(id) ON DELETE CASCADE')();
}
@@ -3,6 +3,7 @@ export 'event_mapper.dart';
export 'member_mapper.dart'; export 'member_mapper.dart';
export 'message_mapper.dart'; export 'message_mapper.dart';
export 'pinned_message_mapper.dart'; export 'pinned_message_mapper.dart';
export 'pinned_message_reaction_mapper.dart';
export 'reaction_mapper.dart'; export 'reaction_mapper.dart';
export 'read_mapper.dart'; export 'read_mapper.dart';
export 'user_mapper.dart'; export 'user_mapper.dart';
@@ -51,7 +51,7 @@ extension MessageEntityX on MessageEntity {
/// Useful mapping functions for [Message] /// Useful mapping functions for [Message]
extension MessageX on Message { extension MessageX on Message {
/// Maps a [Message] into [MessageEntity] /// Maps a [Message] into [MessageEntity]
MessageEntity toEntity({String? cid}) => MessageEntity( MessageEntity toEntity({required String cid}) => MessageEntity(
id: id, id: id,
attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), attachments: attachments.map((it) => jsonEncode(it.toData())).toList(),
channelCid: cid, channelCid: cid,
@@ -51,7 +51,8 @@ extension PinnedMessageEntityX on PinnedMessageEntity {
/// Useful mapping functions for [Message] /// Useful mapping functions for [Message]
extension PMessageX on Message { extension PMessageX on Message {
/// Maps a [Message] into [PinnedMessageEntity] /// Maps a [Message] into [PinnedMessageEntity]
PinnedMessageEntity toPinnedEntity({String? cid}) => PinnedMessageEntity( PinnedMessageEntity toPinnedEntity({required String cid}) =>
PinnedMessageEntity(
id: id, id: id,
attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), attachments: attachments.map((it) => jsonEncode(it.toData())).toList(),
channelCid: cid, channelCid: cid,
@@ -0,0 +1,29 @@
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
/// Useful mapping functions for [PinnedMessageReactionEntity]
extension PinnedMessageReactionEntityX on PinnedMessageReactionEntity {
/// Maps a [PinnedMessageReactionEntity] into [Reaction]
Reaction toReaction({User? user}) => Reaction(
extraData: extraData ?? {},
type: type,
createdAt: createdAt,
userId: userId,
user: user,
messageId: messageId,
score: score,
);
}
/// Useful mapping functions for [Reaction]
extension PReactionX on Reaction {
/// Maps a [Reaction] into [ReactionEntity]
PinnedMessageReactionEntity toPinnedEntity() => PinnedMessageReactionEntity(
extraData: extraData,
type: type,
createdAt: createdAt,
userId: userId!,
messageId: messageId!,
score: score,
);
}
@@ -296,25 +296,34 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
} }
@override @override
Future<void> updateMembers(String cid, List<Member> members) { Future<void> bulkUpdateMembers(Map<String, List<Member>> members) {
assert(_debugIsConnected, ''); assert(_debugIsConnected, '');
_logger.info('updateMembers'); _logger.info('bulkUpdateMembers');
return _readProtected(() => db!.memberDao.updateMembers(cid, members)); return _readProtected(() => db!.memberDao.bulkUpdateMembers(members));
} }
@override @override
Future<void> updateMessages(String cid, List<Message> messages) { Future<void> bulkUpdateMessages(Map<String, List<Message>> messages) {
assert(_debugIsConnected, ''); assert(_debugIsConnected, '');
_logger.info('updateMessages'); _logger.info('bulkUpdateMessages');
return _readProtected(() => db!.messageDao.updateMessages(cid, messages)); return _readProtected(() => db!.messageDao.bulkUpdateMessages(messages));
} }
@override @override
Future<void> updatePinnedMessages(String cid, List<Message> messages) { Future<void> bulkUpdatePinnedMessages(Map<String, List<Message>> messages) {
assert(_debugIsConnected, ''); assert(_debugIsConnected, '');
_logger.info('updatePinnedMessages'); _logger.info('bulkUpdatePinnedMessages');
return _readProtected( return _readProtected(
() => db!.pinnedMessageDao.updateMessages(cid, messages), () => db!.pinnedMessageDao.bulkUpdateMessages(messages),
);
}
@override
Future<void> updatePinnedMessageReactions(List<Reaction> reactions) {
assert(_debugIsConnected, '');
_logger.info('updatePinnedMessageReactions');
return _readProtected(
() => db!.pinnedMessageReactionDao.updateReactions(reactions),
); );
} }
@@ -326,10 +335,10 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
} }
@override @override
Future<void> updateReads(String cid, List<Read> reads) { Future<void> bulkUpdateReads(Map<String, List<Read>> reads) {
assert(_debugIsConnected, ''); assert(_debugIsConnected, '');
_logger.info('updateReads'); _logger.info('bulkUpdateReads');
return _readProtected(() => db!.readDao.updateReads(cid, reads)); return _readProtected(() => db!.readDao.bulkUpdateReads(reads));
} }
@override @override
@@ -339,6 +348,18 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
return _readProtected(() => db!.userDao.updateUsers(users)); return _readProtected(() => db!.userDao.updateUsers(users));
} }
@override
Future<void> deletePinnedMessageReactionsByMessageId(
List<String> messageIds,
) {
assert(_debugIsConnected, '');
_logger.info('deletePinnedMessageReactionsByMessageId');
return _readProtected(
() =>
db!.pinnedMessageReactionDao.deleteReactionsByMessageIds(messageIds),
);
}
@override @override
Future<void> deleteReactionsByMessageId(List<String> messageIds) { Future<void> deleteReactionsByMessageId(List<String> messageIds) {
assert(_debugIsConnected, ''); assert(_debugIsConnected, '');
@@ -34,6 +34,12 @@ class MockChatDatabase extends Mock implements MoorChatDatabase {
@override @override
ReactionDao get reactionDao => _reactionDao ??= MockReactionDao(); ReactionDao get reactionDao => _reactionDao ??= MockReactionDao();
PinnedMessageReactionDao? _pinnedMessageReactionDao;
@override
PinnedMessageReactionDao get pinnedMessageReactionDao =>
_pinnedMessageReactionDao ??= MockPinnedMessageReactionDao();
ReadDao? _readDao; ReadDao? _readDao;
@override @override
@@ -70,6 +76,9 @@ class MockMemberDao extends Mock implements MemberDao {}
class MockReactionDao extends Mock implements ReactionDao {} class MockReactionDao extends Mock implements ReactionDao {}
class MockPinnedMessageReactionDao extends Mock
implements PinnedMessageReactionDao {}
class MockReadDao extends Mock implements ReadDao {} class MockReadDao extends Mock implements ReadDao {}
class MockChannelQueryDao extends Mock implements ChannelQueryDao {} class MockChannelQueryDao extends Mock implements ChannelQueryDao {}
@@ -61,12 +61,73 @@ void main() {
expect(updatedChannel.cid, cid); expect(updatedChannel.cid, cid);
expect(updatedChannel.type, type); expect(updatedChannel.type, type);
//Saving a dummy user
const userId = 'userId';
final dummyUser = User(id: userId);
await database.userDao.updateUsers([dummyUser]);
// Saving a dummy member
final dummyMember = Member(userId: userId, user: dummyUser);
await database.memberDao.updateMembers(cid, [dummyMember]);
// Should match the dummy member
final updatedMembers = await database.memberDao.getMembersByCid(cid);
expect(updatedMembers.length, 1);
expect(updatedMembers.first.userId, userId);
// Saving a dummy message
const messageId = 'messageId';
final dummyMessage = Message(id: messageId, user: dummyUser);
await database.messageDao.updateMessages(cid, [dummyMessage]);
// Should match the dummy message
final updatedMessages = await database.messageDao.getMessagesByCid(cid);
expect(updatedMessages.length, 1);
expect(updatedMessages.first.id, messageId);
// Saving a dummy read
final dummyRead = Read(lastRead: DateTime.now(), user: dummyUser);
await database.readDao.updateReads(cid, [dummyRead]);
// Should match the dummy read
final updatedReads = await database.readDao.getReadsByCid(cid);
expect(updatedReads.length, 1);
expect(updatedReads.first.user, dummyUser);
// Saving a dummy reaction
final dummyReaction =
Reaction(type: 'type', messageId: messageId, userId: userId);
await database.reactionDao.updateReactions([dummyReaction]);
// Should match the dummy reaction
final updatedReactions =
await database.reactionDao.getReactionsByUserId(messageId, userId);
expect(updatedReactions.length, 1);
expect(updatedReactions.first.messageId, messageId);
// Deleting the dummyChannel using cid // Deleting the dummyChannel using cid
await channelDao.deleteChannelByCids([cid]); await channelDao.deleteChannelByCids([cid]);
// Fetched channel Should be null // Fetched channel Should be null
final channel = await channelDao.getChannelByCid(cid); final channel = await channelDao.getChannelByCid(cid);
expect(channel, isNull); expect(channel, isNull);
// Fetched members for passed cid should be empty
final members = await database.memberDao.getMembersByCid(cid);
expect(members, isEmpty);
// Fetched messages for passed cid should be empty
final messages = await database.messageDao.getMessagesByCid(cid);
expect(messages, isEmpty);
// Fetched reads for passed cid should be empty
final reads = await database.readDao.getReadsByCid(cid);
expect(reads, isEmpty);
// Fetched readtions for passed message id and user id should be empty
final reactions =
await database.reactionDao.getReactionsByUserId(messageId, userId);
expect(reactions, isEmpty);
}); });
test('cids', () async { test('cids', () async {
@@ -18,6 +18,7 @@ void main() {
}); });
Future<List<Member>> _prepareTestData(String cid) async { Future<List<Member>> _prepareTestData(String cid) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(3, (index) => User(id: 'testUserId$index')); final users = List.generate(3, (index) => User(id: 'testUserId$index'));
final memberList = List.generate( final memberList = List.generate(
3, 3,
@@ -34,12 +35,13 @@ void main() {
), ),
); );
await database.userDao.updateUsers(users); await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await memberDao.updateMembers(cid, memberList); await memberDao.updateMembers(cid, memberList);
return memberList; return memberList;
} }
test('getMembersByCid', () async { test('getMembersByCid', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Should be empty initially // Should be empty initially
final members = await memberDao.getMembersByCid(cid); final members = await memberDao.getMembersByCid(cid);
@@ -70,7 +72,7 @@ void main() {
}); });
test('updateMembers', () async { test('updateMembers', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final memberList = await _prepareTestData(cid); final memberList = await _prepareTestData(cid);
@@ -132,7 +134,7 @@ void main() {
}); });
test('deleteMemberByCids', () async { test('deleteMemberByCids', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final members = await _prepareTestData(cid); final members = await _prepareTestData(cid);
@@ -23,6 +23,7 @@ void main() {
bool mapAllThreadToFirstMessage = false, bool mapAllThreadToFirstMessage = false,
int count = 3, int count = 3,
}) async { }) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index')); final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final messages = List.generate( final messages = List.generate(
count, count,
@@ -98,13 +99,20 @@ void main() {
if (quoted) ...quotedMessages, if (quoted) ...quotedMessages,
if (threads) ...threadMessages if (threads) ...threadMessages
]; ];
final reaction = Reaction(
type: 'type',
messageId: allMessages.first.id,
user: users.first,
);
await database.userDao.updateUsers(users); await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await messageDao.updateMessages(cid, allMessages); await messageDao.updateMessages(cid, allMessages);
await database.reactionDao.updateReactions([reaction]);
return allMessages; return allMessages;
} }
test('deleteMessageByIds', () async { test('deleteMessageByIds', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final insertedMessages = await _prepareTestData(cid); final insertedMessages = await _prepareTestData(cid);
@@ -113,66 +121,112 @@ void main() {
final messages = await messageDao.getMessagesByCid(cid); final messages = await messageDao.getMessagesByCid(cid);
expect(messages.length, insertedMessages.length); expect(messages.length, insertedMessages.length);
final firstMessageId = messages.first.id;
// Fetched reactions list should have one reaction for given message id
final reactions = await database.reactionDao.getReactions(firstMessageId);
expect(reactions.length, 1);
// Deleting 2 messages from DB // Deleting 2 messages from DB
await messageDao.deleteMessageByIds( await messageDao.deleteMessageByIds(
['testMessageId${cid}0', 'testMessageId${cid}1'], [firstMessageId, 'testMessageId${cid}1'],
); );
// New fetched messages length should 2 less than the // New fetched messages length should 2 less than the
// previous fetched messages // previous fetched messages
final newMessages = await messageDao.getMessagesByCid(cid); final newMessages = await messageDao.getMessagesByCid(cid);
expect(newMessages.length, messages.length - 2); expect(newMessages.length, messages.length - 2);
// Reaction for the first message should be deleted too
final newReactions =
await database.reactionDao.getReactions(firstMessageId);
expect(newReactions, isEmpty);
}); });
group('deleteMessageByCids', () { group('deleteMessageByCids', () {
const cid1 = 'testCid1'; const cid1 = 'test:Cid1';
const cid2 = 'testCid2'; const cid2 = 'test:Cid2';
test('should delete all the messages of first channel', () async { test(
// Preparing test data 'should delete all the messages and reactions of first channel',
final cid1InsertedMessages = await _prepareTestData(cid1); () async {
final cid2InsertedMessages = await _prepareTestData(cid2); // Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length // Fetched message list should match the test message list length
final cid1Messages = await messageDao.getMessagesByCid(cid1); final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2Messages = await messageDao.getMessagesByCid(cid2); final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length); expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length); expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1 // Fetched reactions list should have one reaction for given message id
await messageDao.deleteMessageByCids([cid1]); final cid1firstMessageId = cid1Messages.first.id;
final cid1Reactions =
await database.reactionDao.getReactions(cid1firstMessageId);
expect(cid1Reactions.length, 1);
// Fetched messages length of only cid1 should be empty // Deleting all the messages of cid1
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1); await messageDao.deleteMessageByCids([cid1]);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
});
test('should delete all the messages of both channel', () async { // Fetched messages length of only cid1 should be empty
// Preparing test data final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid1InsertedMessages = await _prepareTestData(cid1); final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
final cid2InsertedMessages = await _prepareTestData(cid2); expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
// Fetched message list should match the test message list length // Reaction for the first message should be deleted too
final cid1Messages = await messageDao.getMessagesByCid(cid1); final cid1FetchedReactions =
final cid2Messages = await messageDao.getMessagesByCid(cid2); await database.reactionDao.getReactions(cid1firstMessageId);
expect(cid1Messages.length, cid1InsertedMessages.length); expect(cid1FetchedReactions, isEmpty);
expect(cid2Messages.length, cid2InsertedMessages.length); },
);
// Deleting all the messages of cid1 test(
await messageDao.deleteMessageByCids([cid1, cid2]); 'should delete all the messages and reactions of both channel',
() async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched messages length of both cid1 and cid2 should be empty // Fetched message list should match the test message list length
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1); final cid1Messages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2); final cid2Messages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty); expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2FetchedMessages, isEmpty); expect(cid2Messages.length, cid2InsertedMessages.length);
});
// Fetched reactions list should have one reaction for given message id
final cid1FirstMessageId = cid1Messages.first.id;
final cid1Reactions =
await database.reactionDao.getReactions(cid1FirstMessageId);
expect(cid1Reactions.length, 1);
final cid2FirstMessageId = cid2Messages.first.id;
final cid2Reactions =
await database.reactionDao.getReactions(cid2FirstMessageId);
expect(cid2Reactions.length, 1);
// Deleting all the messages of cid1
await messageDao.deleteMessageByCids([cid1, cid2]);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages = await messageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await messageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
// Reaction for the first message should be deleted too
final cid1FetchedReactions =
await database.reactionDao.getReactions(cid1FirstMessageId);
expect(cid1FetchedReactions, isEmpty);
final cid2FetchedReactions =
await database.reactionDao.getReactions(cid2FirstMessageId);
expect(cid2FetchedReactions, isEmpty);
},
);
}); });
test('getMessageById', () async { test('getMessageById', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const id = 'testMessageId${cid}0'; const id = 'testMessageId${cid}0';
// Should be null initially // Should be null initially
@@ -190,7 +244,7 @@ void main() {
}); });
test('getThreadMessages', () async { test('getThreadMessages', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Messages should be empty initially // Messages should be empty initially
final messages = await messageDao.getThreadMessages(cid); final messages = await messageDao.getThreadMessages(cid);
@@ -209,7 +263,7 @@ void main() {
}); });
test('getThreadMessagesByParentId', () async { test('getThreadMessagesByParentId', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0'; const parentId = 'testMessageId${cid}0';
// Messages should be empty initially // Messages should be empty initially
@@ -228,7 +282,7 @@ void main() {
}); });
test('getThreadMessagesByParentId along with pagination', () async { test('getThreadMessagesByParentId along with pagination', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0'; const parentId = 'testMessageId${cid}0';
const options = PaginationParams( const options = PaginationParams(
limit: 15, limit: 15,
@@ -262,7 +316,7 @@ void main() {
}); });
test('getMessagesByCid', () async { test('getMessagesByCid', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Should be empty initially // Should be empty initially
final messages = await messageDao.getMessagesByCid(cid); final messages = await messageDao.getMessagesByCid(cid);
@@ -283,7 +337,7 @@ void main() {
}); });
test('getMessagesByCid along with quotedMessage', () async { test('getMessagesByCid along with quotedMessage', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Should be empty initially // Should be empty initially
final messages = await messageDao.getMessagesByCid(cid); final messages = await messageDao.getMessagesByCid(cid);
@@ -301,7 +355,7 @@ void main() {
}); });
test('getMessagesByCid along with pagination', () async { test('getMessagesByCid along with pagination', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const limit = 15; const limit = 15;
const lessThan = 'testMessageId${cid}25'; const lessThan = 'testMessageId${cid}25';
const greaterThanOrEqual = 'testMessageId${cid}5'; const greaterThanOrEqual = 'testMessageId${cid}5';
@@ -333,7 +387,7 @@ void main() {
}); });
test('updateMessages', () async { test('updateMessages', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final insertedMessages = await _prepareTestData(cid); final insertedMessages = await _prepareTestData(cid);
@@ -23,6 +23,7 @@ void main() {
bool mapAllThreadToFirstMessage = false, bool mapAllThreadToFirstMessage = false,
int count = 3, int count = 3,
}) async { }) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index')); final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final messages = List.generate( final messages = List.generate(
count, count,
@@ -83,13 +84,20 @@ void main() {
if (quoted) ...quotedMessages, if (quoted) ...quotedMessages,
if (threads) ...threadMessages if (threads) ...threadMessages
]; ];
final reaction = Reaction(
type: 'type',
messageId: allMessages.first.id,
user: users.first,
);
await database.userDao.updateUsers(users); await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await pinnedMessageDao.updateMessages(cid, allMessages); await pinnedMessageDao.updateMessages(cid, allMessages);
await database.pinnedMessageReactionDao.updateReactions([reaction]);
return allMessages; return allMessages;
} }
test('deleteMessageByIds', () async { test('deleteMessageByIds', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final insertedMessages = await _prepareTestData(cid); final insertedMessages = await _prepareTestData(cid);
@@ -98,66 +106,117 @@ void main() {
final messages = await pinnedMessageDao.getMessagesByCid(cid); final messages = await pinnedMessageDao.getMessagesByCid(cid);
expect(messages.length, insertedMessages.length); expect(messages.length, insertedMessages.length);
final firstMessageId = messages.first.id;
// Fetched reactions list should have one reaction for given message id
final reactions =
await database.pinnedMessageReactionDao.getReactions(firstMessageId);
expect(reactions.length, 1);
// Deleting 2 messages from DB // Deleting 2 messages from DB
await pinnedMessageDao.deleteMessageByIds( await pinnedMessageDao.deleteMessageByIds(
['testMessageId${cid}0', 'testMessageId${cid}1'], [firstMessageId, 'testMessageId${cid}1'],
); );
// New fetched messages length should 2 less than the // New fetched messages length should 2 less than the
// previous fetched messages // previous fetched messages
final newMessages = await pinnedMessageDao.getMessagesByCid(cid); final newMessages = await pinnedMessageDao.getMessagesByCid(cid);
expect(newMessages.length, messages.length - 2); expect(newMessages.length, messages.length - 2);
// Reaction for the first message should be deleted too
final newReactions =
await database.pinnedMessageReactionDao.getReactions(firstMessageId);
expect(newReactions, isEmpty);
}); });
group('deleteMessageByCids', () { group('deleteMessageByCids', () {
const cid1 = 'testCid1'; const cid1 = 'test:Cid1';
const cid2 = 'testCid2'; const cid2 = 'test:Cid2';
test('should delete all the messages of first channel', () async { test(
// Preparing test data 'should delete all the messages and reactions of first channel',
final cid1InsertedMessages = await _prepareTestData(cid1); () async {
final cid2InsertedMessages = await _prepareTestData(cid2); // Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched message list should match the test message list length // Fetched message list should match the test message list length
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1Messages.length, cid1InsertedMessages.length); expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2Messages.length, cid2InsertedMessages.length); expect(cid2Messages.length, cid2InsertedMessages.length);
// Deleting all the messages of cid1 // Fetched reactions list should have one reaction for given message id
await pinnedMessageDao.deleteMessageByCids([cid1]); final cid1firstMessageId = cid1Messages.first.id;
final cid1Reactions = await database.pinnedMessageReactionDao
.getReactions(cid1firstMessageId);
expect(cid1Reactions.length, 1);
// Fetched messages length of only cid1 should be empty // Deleting all the messages of cid1
final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); await pinnedMessageDao.deleteMessageByCids([cid1]);
final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
});
test('should delete all the messages of both channel', () async { // Fetched messages length of only cid1 should be empty
// Preparing test data final cid1FetchedMessages =
final cid1InsertedMessages = await _prepareTestData(cid1); await pinnedMessageDao.getMessagesByCid(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2); final cid2FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isNotEmpty);
// Fetched message list should match the test message list length // Reaction for the first message should be deleted too
final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); final cid1FetchedReactions = await database.pinnedMessageReactionDao
final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); .getReactions(cid1firstMessageId);
expect(cid1Messages.length, cid1InsertedMessages.length); expect(cid1FetchedReactions, isEmpty);
expect(cid2Messages.length, cid2InsertedMessages.length); },
);
// Deleting all the messages of cid1 test(
await pinnedMessageDao.deleteMessageByCids([cid1, cid2]); 'should delete all the messages and reactions of both channel',
() async {
// Preparing test data
final cid1InsertedMessages = await _prepareTestData(cid1);
final cid2InsertedMessages = await _prepareTestData(cid2);
// Fetched messages length of both cid1 and cid2 should be empty // Fetched message list should match the test message list length
final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty); expect(cid1Messages.length, cid1InsertedMessages.length);
expect(cid2FetchedMessages, isEmpty); expect(cid2Messages.length, cid2InsertedMessages.length);
});
// Fetched reactions list should have one reaction for given message id
final cid1FirstMessageId = cid1Messages.first.id;
final cid1Reactions = await database.pinnedMessageReactionDao
.getReactions(cid1FirstMessageId);
expect(cid1Reactions.length, 1);
final cid2FirstMessageId = cid2Messages.first.id;
final cid2Reactions = await database.pinnedMessageReactionDao
.getReactions(cid2FirstMessageId);
expect(cid2Reactions.length, 1);
// Deleting all the messages of cid1
await pinnedMessageDao.deleteMessageByCids([cid1, cid2]);
// Fetched messages length of both cid1 and cid2 should be empty
final cid1FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid1);
final cid2FetchedMessages =
await pinnedMessageDao.getMessagesByCid(cid2);
expect(cid1FetchedMessages, isEmpty);
expect(cid2FetchedMessages, isEmpty);
// Reaction for the first message should be deleted too
final cid1FetchedReactions = await database.pinnedMessageReactionDao
.getReactions(cid1FirstMessageId);
expect(cid1FetchedReactions, isEmpty);
final cid2FetchedReactions = await database.pinnedMessageReactionDao
.getReactions(cid2FirstMessageId);
expect(cid2FetchedReactions, isEmpty);
},
);
}); });
test('getMessageById', () async { test('getMessageById', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const id = 'testMessageId${cid}0'; const id = 'testMessageId${cid}0';
// Should be null initially // Should be null initially
@@ -175,7 +234,7 @@ void main() {
}); });
test('getThreadMessages', () async { test('getThreadMessages', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Messages should be empty initially // Messages should be empty initially
final messages = await pinnedMessageDao.getThreadMessages(cid); final messages = await pinnedMessageDao.getThreadMessages(cid);
@@ -194,7 +253,7 @@ void main() {
}); });
test('getThreadMessagesByParentId', () async { test('getThreadMessagesByParentId', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0'; const parentId = 'testMessageId${cid}0';
// Messages should be empty initially // Messages should be empty initially
@@ -214,7 +273,7 @@ void main() {
}); });
test('getThreadMessagesByParentId along with pagination', () async { test('getThreadMessagesByParentId along with pagination', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const parentId = 'testMessageId${cid}0'; const parentId = 'testMessageId${cid}0';
const options = PaginationParams( const options = PaginationParams(
limit: 15, limit: 15,
@@ -248,7 +307,7 @@ void main() {
}); });
test('getMessagesByCid', () async { test('getMessagesByCid', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Should be empty initially // Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(cid); final messages = await pinnedMessageDao.getMessagesByCid(cid);
@@ -269,7 +328,7 @@ void main() {
}); });
test('getMessagesByCid along with quotedMessage', () async { test('getMessagesByCid along with quotedMessage', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Should be empty initially // Should be empty initially
final messages = await pinnedMessageDao.getMessagesByCid(cid); final messages = await pinnedMessageDao.getMessagesByCid(cid);
@@ -287,7 +346,7 @@ void main() {
}); });
test('getMessagesByCid along with pagination', () async { test('getMessagesByCid along with pagination', () async {
const cid = 'testCid'; const cid = 'test:Cid';
const limit = 15; const limit = 15;
const lessThan = 'testMessageId${cid}25'; const lessThan = 'testMessageId${cid}25';
const greaterThanOrEqual = 'testMessageId${cid}5'; const greaterThanOrEqual = 'testMessageId${cid}5';
@@ -319,7 +378,7 @@ void main() {
}); });
test('updateMessages', () async { test('updateMessages', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final insertedMessages = await _prepareTestData(cid); final insertedMessages = await _prepareTestData(cid);
@@ -0,0 +1,205 @@
import 'dart:math' as math;
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/dao/pinned_message_reaction_dao.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:test/test.dart';
import '../../stream_chat_persistence_client_test.dart';
void main() {
late PinnedMessageReactionDao pinnedMessageReactionDao;
late MoorChatDatabase database;
setUp(() {
database = testDatabaseProvider('testUserId');
pinnedMessageReactionDao = database.pinnedMessageReactionDao;
});
Future<List<Reaction>> _prepareReactionData(
String messageId, {
String? userId,
int count = 3,
}) async {
const cid = 'test:Cid';
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final message = Message(
id: messageId,
type: 'testType',
user: users.first,
createdAt: DateTime.now(),
shadowed: math.Random().nextBool(),
showInChannel: math.Random().nextBool(),
replyCount: 3,
updatedAt: DateTime.now(),
extraData: const {'extra_test_field': 'extraTestData'},
text: 'Dummy text',
pinned: math.Random().nextBool(),
pinnedAt: DateTime.now(),
pinnedBy: users.first,
);
final reactions = List.generate(
count,
(index) => Reaction(
type: 'testType$index',
createdAt: DateTime.now(),
userId: userId ?? users[index].id,
messageId: message.id,
score: count + 3,
extraData: {'extra_test_field': 'extraTestData'},
),
);
await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await database.pinnedMessageDao.updateMessages(cid, [message]);
await pinnedMessageReactionDao.updateReactions(reactions);
return reactions;
}
test('getReactions', () async {
const messageId = 'testMessageId';
// Should be empty initially
final reactions = await pinnedMessageReactionDao.getReactions(messageId);
expect(reactions, isEmpty);
// Adding sample reactions
final insertedReactions = await _prepareReactionData(messageId);
expect(insertedReactions, isNotEmpty);
// Fetched reaction length should match inserted reactions length.
// Every reaction messageId should match the provided messageId.
final fetchedReactions =
await pinnedMessageReactionDao.getReactions(messageId);
expect(fetchedReactions.length, insertedReactions.length);
expect(fetchedReactions.every((it) => it.messageId == messageId), true);
});
test('getReactionsByUserId', () async {
const messageId = 'testMessageId';
const userId = 'testUserId';
// Should be empty initially
final reactions =
await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId);
expect(reactions, isEmpty);
// Adding sample reactions
final insertedReactions =
await _prepareReactionData(messageId, userId: userId);
expect(insertedReactions, isNotEmpty);
// Fetched reaction length should match inserted reactions length.
// Every reaction messageId should match the provided messageId.
// Every reaction userId should match the provided userId.
final fetchedReactions =
await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId);
expect(fetchedReactions.length, insertedReactions.length);
expect(fetchedReactions.every((it) => it.messageId == messageId), true);
expect(fetchedReactions.every((it) => it.userId == userId), true);
});
test('updateReactions', () async {
const messageId = 'testMessageId';
// Preparing test data
final reactions = await _prepareReactionData(messageId);
// Modifying one of the reaction and also adding one new
final copyReaction = reactions.first.copyWith(score: 33);
final newReaction = Reaction(
type: 'testType3',
createdAt: DateTime.now(),
userId: 'testUserId3',
messageId: messageId,
score: 30,
extraData: {'extra_test_field': 'extraTestData'},
);
await pinnedMessageReactionDao.updateReactions([copyReaction, newReaction]);
// Fetched reaction length should be one more than inserted reactions.
// copyReaction `score` modified field should be 33.
// Fetched reactions should contain the newReaction.
final fetchedReactions =
await pinnedMessageReactionDao.getReactions(messageId);
expect(fetchedReactions.length, reactions.length + 1);
expect(
fetchedReactions
.firstWhere((it) =>
it.userId == copyReaction.userId && it.type == copyReaction.type)
.score,
33,
);
expect(
fetchedReactions
.where((it) =>
it.userId == newReaction.userId && it.type == newReaction.type)
.isNotEmpty,
true,
);
});
group('deleteReactionsByMessageIds', () {
const messageId1 = 'testMessageId1';
const messageId2 = 'testMessageId2';
test('should delete all the reactions of first message', () async {
// Preparing test data
final insertedReactions1 = await _prepareReactionData(messageId1);
final insertedReactions2 = await _prepareReactionData(messageId2);
// Fetched reaction list length should match
// the inserted reactions list length
final reactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final reactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(reactions1.length, insertedReactions1.length);
expect(reactions2.length, insertedReactions2.length);
// Deleting all the reactions of messageId1
await pinnedMessageReactionDao.deleteReactionsByMessageIds([messageId1]);
// Fetched reactions length of only messageId1 should be empty
final fetchedReactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final fetchedReactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(fetchedReactions1, isEmpty);
expect(fetchedReactions2, isNotEmpty);
});
test('should delete all the messages of both message', () async {
// Preparing test data
final insertedReactions1 = await _prepareReactionData(messageId1);
final insertedReactions2 = await _prepareReactionData(messageId2);
// Fetched reaction list length should match
// the inserted reactions list length
final reactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final reactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(reactions1.length, insertedReactions1.length);
expect(reactions2.length, insertedReactions2.length);
// Deleting all the reactions of messageId1 and messageId2
await pinnedMessageReactionDao
.deleteReactionsByMessageIds([messageId1, messageId2]);
// Fetched reactions length of both messages should be empty
final fetchedReactions1 =
await pinnedMessageReactionDao.getReactions(messageId1);
final fetchedReactions2 =
await pinnedMessageReactionDao.getReactions(messageId2);
expect(fetchedReactions1, isEmpty);
expect(fetchedReactions2, isEmpty);
});
});
tearDown(() async {
await database.disconnect();
});
}
@@ -21,6 +21,8 @@ void main() {
String? userId, String? userId,
int count = 3, int count = 3,
}) async { }) async {
const cid = 'test:Cid';
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index')); final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final message = Message( final message = Message(
id: messageId, id: messageId,
@@ -50,7 +52,8 @@ void main() {
); );
await database.userDao.updateUsers(users); await database.userDao.updateUsers(users);
await database.messageDao.updateMessages('testCid', [message]); await database.channelDao.updateChannels(channels);
await database.messageDao.updateMessages(cid, [message]);
await reactionDao.updateReactions(reactions); await reactionDao.updateReactions(reactions);
return reactions; return reactions;
@@ -16,6 +16,7 @@ void main() {
}); });
Future<List<Read>> _prepareReadData(String cid, {int count = 3}) async { Future<List<Read>> _prepareReadData(String cid, {int count = 3}) async {
final channels = [ChannelModel(cid: cid)];
final users = List.generate(count, (index) => User(id: 'testUserId$index')); final users = List.generate(count, (index) => User(id: 'testUserId$index'));
final reads = List.generate( final reads = List.generate(
count, count,
@@ -27,12 +28,13 @@ void main() {
); );
await database.userDao.updateUsers(users); await database.userDao.updateUsers(users);
await database.channelDao.updateChannels(channels);
await readDao.updateReads(cid, reads); await readDao.updateReads(cid, reads);
return reads; return reads;
} }
test('getReadsByCid', () async { test('getReadsByCid', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Should be empty initially // Should be empty initially
final reads = await readDao.getReadsByCid(cid); final reads = await readDao.getReadsByCid(cid);
@@ -55,7 +57,7 @@ void main() {
}); });
test('updateReads', () async { test('updateReads', () async {
const cid = 'testCid'; const cid = 'test:Cid';
// Preparing test data // Preparing test data
final insertedReads = await _prepareReadData(cid); final insertedReads = await _prepareReadData(cid);
@@ -0,0 +1,52 @@
import 'package:test/test.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
import 'package:stream_chat_persistence/src/mapper/pinned_message_reaction_mapper.dart';
import '../utils/date_matcher.dart';
void main() {
test('toReaction should map the entity into Reaction', () {
final user = User(id: 'testUserId');
final message = Message(id: 'testMessageId');
final entity = PinnedMessageReactionEntity(
userId: user.id,
messageId: message.id,
type: 'haha',
score: 33,
createdAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
);
final reaction = entity.toReaction(user: user);
expect(reaction, isA<Reaction>());
expect(reaction.userId, entity.userId);
expect(reaction.messageId, entity.messageId);
expect(reaction.type, entity.type);
expect(reaction.score, entity.score);
expect(reaction.createdAt, isSameDateAs(entity.createdAt));
expect(reaction.extraData, entity.extraData);
});
test('toEntity should map reaction into PinnedMessageReactionEntity', () {
final user = User(id: 'testUserId');
final message = Message(id: 'testMessageId');
final reaction = Reaction(
userId: user.id,
messageId: message.id,
type: 'haha',
score: 33,
createdAt: DateTime.now(),
extraData: {'extra_test_data': 'extraData'},
);
final entity = reaction.toPinnedEntity();
expect(entity, isA<PinnedMessageReactionEntity>());
expect(entity.userId, reaction.userId);
expect(entity.messageId, reaction.messageId);
expect(entity.type, reaction.type);
expect(entity.score, reaction.score);
expect(entity.createdAt, isSameDateAs(reaction.createdAt));
expect(entity.extraData, reaction.extraData);
});
}
@@ -397,23 +397,26 @@ void main() {
test('updateMessages', () async { test('updateMessages', () async {
const cid = 'testCid'; const cid = 'testCid';
final messages = List.generate(3, (index) => Message()); final messages = List.generate(3, (index) => Message());
when(() => mockDatabase.messageDao.updateMessages(cid, messages))
when(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages}))
.thenAnswer((_) => Future.value()); .thenAnswer((_) => Future.value());
await client.updateMessages(cid, messages); await client.updateMessages(cid, messages);
verify(() => mockDatabase.messageDao.updateMessages(cid, messages)) verify(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages}))
.called(1); .called(1);
}); });
test('updatePinnedMessages', () async { test('updatePinnedMessages', () async {
const cid = 'testCid'; const cid = 'testCid';
final messages = List.generate(3, (index) => Message()); final messages = List.generate(3, (index) => Message());
when(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages)) when(
.thenAnswer((_) => Future.value()); () => mockDatabase.pinnedMessageDao.bulkUpdateMessages({cid: messages}),
).thenAnswer((_) => Future.value());
await client.updatePinnedMessages(cid, messages); await client.updatePinnedMessages(cid, messages);
verify(() => mockDatabase.pinnedMessageDao.updateMessages(cid, messages)) verify(
.called(1); () => mockDatabase.pinnedMessageDao.bulkUpdateMessages({cid: messages}),
).called(1);
}); });
test('getChannelThreads', () async { test('getChannelThreads', () async {
@@ -456,11 +459,11 @@ void main() {
test('updateMembers', () async { test('updateMembers', () async {
const cid = 'testCid'; const cid = 'testCid';
final members = List.generate(3, (index) => Member()); final members = List.generate(3, (index) => Member());
when(() => mockDatabase.memberDao.updateMembers(cid, members)) when(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members}))
.thenAnswer((_) => Future.value()); .thenAnswer((_) => Future.value());
await client.updateMembers(cid, members); await client.updateMembers(cid, members);
verify(() => mockDatabase.memberDao.updateMembers(cid, members)) verify(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members}))
.called(1); .called(1);
}); });
@@ -473,11 +476,12 @@ void main() {
lastRead: DateTime.now(), lastRead: DateTime.now(),
), ),
); );
when(() => mockDatabase.readDao.updateReads(cid, reads)) when(() => mockDatabase.readDao.bulkUpdateReads({cid: reads}))
.thenAnswer((_) => Future.value()); .thenAnswer((_) => Future.value());
await client.updateReads(cid, reads); await client.updateReads(cid, reads);
verify(() => mockDatabase.readDao.updateReads(cid, reads)).called(1); verify(() => mockDatabase.readDao.bulkUpdateReads({cid: reads}))
.called(1);
}); });
test('updateUsers', () async { test('updateUsers', () async {
@@ -502,6 +506,21 @@ void main() {
.called(1); .called(1);
}); });
test('updatePinnedMessageReactions', () async {
final reactions = List.generate(
3,
(index) => Reaction(type: 'testType$index'),
);
when(() =>
mockDatabase.pinnedMessageReactionDao.updateReactions(reactions))
.thenAnswer((_) => Future.value());
await client.updatePinnedMessageReactions(reactions);
verify(() =>
mockDatabase.pinnedMessageReactionDao.updateReactions(reactions))
.called(1);
});
test('deleteReactionsByMessageId', () async { test('deleteReactionsByMessageId', () async {
final messageIds = <String>[]; final messageIds = <String>[];
when(() => when(() =>
@@ -514,6 +533,17 @@ void main() {
.called(1); .called(1);
}); });
test('deletePinnedMessageReactionsByMessageId', () async {
final messageIds = <String>[];
when(() => mockDatabase.pinnedMessageReactionDao
.deleteReactionsByMessageIds(messageIds))
.thenAnswer((_) => Future.value());
await client.deletePinnedMessageReactionsByMessageId(messageIds);
verify(() => mockDatabase.pinnedMessageReactionDao
.deleteReactionsByMessageIds(messageIds)).called(1);
});
test('deleteMembersByCids', () async { test('deleteMembersByCids', () async {
final cids = <String>[]; final cids = <String>[];
when(() => mockDatabase.memberDao.deleteMemberByCids(cids)) when(() => mockDatabase.memberDao.deleteMemberByCids(cids))