Skip to content

Commit

Permalink
feat(persistence): add support for polls and poll votes (#2060)
Browse files Browse the repository at this point in the history
Co-authored-by: Deven Joshi <[email protected]>
Co-authored-by: xsahil03x <[email protected]>
  • Loading branch information
3 people authored Dec 30, 2024
1 parent 337074e commit fa7ae0f
Show file tree
Hide file tree
Showing 39 changed files with 6,268 additions and 2,969 deletions.
10 changes: 7 additions & 3 deletions .github/actions/pana/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ runs:
using: "composite"
steps:
- name: Temporary Override Local Dependencies
shell: bash
run: |
git apply .github/actions/pana/chore__temporarily_override_dep_to_local.patch
uses: mikefarah/yq@master
with:
cmd: |
yq eval '.dependencies.stream_chat_flutter = {"path": "../stream_chat_flutter"}' -i packages/stream_chat_localizations/pubspec.yaml
yq eval '.dependencies.stream_chat = {"path": "../stream_chat"}' -i packages/stream_chat_flutter_core/pubspec.yaml
yq eval '.dependencies.stream_chat_flutter_core = {"path": "../stream_chat_flutter_core"}' -i packages/stream_chat_flutter/pubspec.yaml
yq eval '.dependencies.stream_chat = {"path": "../stream_chat"}' -i packages/stream_chat_persistence/pubspec.yaml
- name: Install Flutter
uses: subosito/flutter-action@v2
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ command:
rxdart: ^0.28.0
share_plus: ^10.0.2
shimmer: ^3.0.0
sqlite3_flutter_libs: ^0.5.24
sqlite3_flutter_libs: ^0.5.26
stream_chat: ^8.3.0
stream_chat_flutter: ^8.3.0
stream_chat_flutter_core: ^8.3.0
Expand Down
2 changes: 2 additions & 0 deletions packages/stream_chat/lib/src/core/models/poll.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ const _nullConst = _NullConst();
/// {@endtemplate}
enum VotingVisibility {
/// The voting process is anonymous.
@JsonValue('anonymous')
anonymous,

/// The voting process is public.
@JsonValue('public')
public,
}

Expand Down
22 changes: 22 additions & 0 deletions packages/stream_chat/lib/src/core/models/poll_vote.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ class PollVote extends Equatable {
/// Serialize to json
Map<String, dynamic> toJson() => _$PollVoteToJson(this);

/// Creates a copy of [PollVote] with specified attributes overridden.
PollVote copyWith({
String? id,
String? pollId,
String? optionId,
String? answerText,
DateTime? createdAt,
DateTime? updatedAt,
String? userId,
User? user,
}) =>
PollVote(
id: id ?? this.id,
pollId: pollId ?? this.pollId,
optionId: optionId ?? this.optionId,
answerText: answerText ?? this.answerText,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
userId: userId ?? this.userId,
user: user ?? this.user,
);

@override
List<Object?> get props => [
id,
Expand Down
126 changes: 83 additions & 43 deletions packages/stream_chat/lib/src/db/chat_persistence_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import 'package:stream_chat/src/core/models/event.dart';
import 'package:stream_chat/src/core/models/filter.dart';
import 'package:stream_chat/src/core/models/member.dart';
import 'package:stream_chat/src/core/models/message.dart';
import 'package:stream_chat/src/core/models/poll.dart';
import 'package:stream_chat/src/core/models/poll_vote.dart';
import 'package:stream_chat/src/core/models/reaction.dart';
import 'package:stream_chat/src/core/models/read.dart';
import 'package:stream_chat/src/core/models/user.dart';
Expand Down Expand Up @@ -169,6 +171,12 @@ abstract class ChatPersistenceClient {
/// Updates all the channels using the new [channels] data.
Future<void> updateChannels(List<ChannelModel> channels);

/// Updates all the polls using the new [polls] data.
Future<void> updatePolls(List<Poll> polls);

/// Deletes all the polls by [pollIds].
Future<void> deletePollsByIds(List<String> pollIds);

/// Updates all the members of a particular channle [cid]
/// with the new [members] data
Future<void> updateMembers(String cid, List<Member> members) =>
Expand All @@ -194,12 +202,18 @@ abstract class ChatPersistenceClient {
/// Updates the pinned message reactions data with the new [reactions] data
Future<void> updatePinnedMessageReactions(List<Reaction> reactions);

/// Updates the poll votes data with the new [pollVotes] data
Future<void> updatePollVotes(List<PollVote> pollVotes);

/// Deletes all the reactions by [messageIds]
Future<void> deleteReactionsByMessageId(List<String> messageIds);

/// Deletes all the pinned messages reactions by [messageIds]
Future<void> deletePinnedMessageReactionsByMessageId(List<String> messageIds);

/// Deletes all the poll votes by [pollIds]
Future<void> deletePollVotesByPollIds(List<String> pollIds);

/// Deletes all the members by channel [cids]
Future<void> deleteMembersByCids(List<String> cids);

Expand Down Expand Up @@ -245,50 +259,64 @@ abstract class ChatPersistenceClient {
final reactions = <Reaction>[];
final pinnedReactions = <Reaction>[];

final polls = <Poll>[];
final pollVotes = <PollVote>[];
final pollVotesToDelete = <String>[];

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 Iterable<Message>? messages;
if (CurrentPlatform.isWeb) {
messages = state.messages?.where(
// Continue if channel is not available.
if (channel == null) continue;
channels.add(channel);

final cid = channel.cid;
final reads = state.read;
final members = state.members;
final messages = switch (CurrentPlatform.isWeb) {
true => state.messages?.where(
(it) => !it.attachments.any(
(it) => it.uploadState != const UploadState.success(),
),
);
} else {
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?.toList();
channelWithPinnedMessages[cid] = pinnedMessages;

reactions.addAll(messages?.expand(_expandReactions) ?? []);
pinnedReactions.addAll(pinnedMessages?.expand(_expandReactions) ?? []);

users.addAll([
channel.createdBy,
...messages?.map((it) => it.user) ?? <User>[],
...reads?.map((it) => it.user) ?? <User>[],
...members?.map((it) => it.user) ?? <User>[],
...reactions.map((it) => it.user),
...pinnedReactions.map((it) => it.user),
].withNullifyer);
}
),
_ => state.messages,
};

final pinnedMessages = state.pinnedMessages;

// Preparing deletion data
membersToDelete.add(cid);
reactionsToDelete.addAll(messages?.map((it) => it.id) ?? []);
pinnedReactionsToDelete.addAll(pinnedMessages?.map((it) => it.id) ?? []);

// preparing addition data
channelWithReads[cid] = reads;
channelWithMembers[cid] = members;
channelWithMessages[cid] = messages?.toList();
channelWithPinnedMessages[cid] = pinnedMessages;

reactions.addAll(messages?.expand(_expandReactions) ?? []);
pinnedReactions.addAll(pinnedMessages?.expand(_expandReactions) ?? []);

polls.addAll([
...?messages?.map((it) => it.poll),
...?pinnedMessages?.map((it) => it.poll),
].withNullifyer);

pollVotesToDelete.addAll(polls.map((it) => it.id));

pollVotes.addAll(polls.expand(_expandPollVotes));

users.addAll([
channel.createdBy,
...?messages?.map((it) => it.user),
...?pinnedMessages?.map((it) => it.user),
...?reads?.map((it) => it.user),
...?members?.map((it) => it.user),
...reactions.map((it) => it.user),
...pinnedReactions.map((it) => it.user),
...polls.map((it) => it.createdBy),
...pollVotes.map((it) => it.user),
].withNullifyer);
}

// Removing old members and reactions data as they may have
Expand All @@ -297,12 +325,14 @@ abstract class ChatPersistenceClient {
deleteMembersByCids(membersToDelete),
deleteReactionsByMessageId(reactionsToDelete),
deletePinnedMessageReactionsByMessageId(pinnedReactionsToDelete),
deletePollVotesByPollIds(pollVotesToDelete),
]);

// Updating first as does not depend on any other table.
await Future.wait([
updateUsers(users.toList(growable: false)),
updateChannels(channels.toList(growable: false)),
updatePolls(polls.toList(growable: false)),
]);

// All has a foreign key relation with channels table.
Expand All @@ -315,10 +345,9 @@ abstract class ChatPersistenceClient {

// Both has a foreign key relation with messages, pinnedMessages table.
await Future.wait([
updateReactions(reactions.toList(growable: false)),
updatePinnedMessageReactions(
pinnedReactions.toList(growable: false),
),
updateReactions(reactions),
updatePinnedMessageReactions(pinnedReactions),
updatePollVotes(pollVotes),
]);
}

Expand All @@ -330,4 +359,15 @@ abstract class ChatPersistenceClient {
if (latest != null) ...latest.where((r) => r.userId != null),
];
}

List<PollVote> _expandPollVotes(Poll poll) {
final latestAnswers = poll.latestAnswers;
final latestVotes = poll.latestVotesByOption.values;
final ownVotesAndAnswers = poll.ownVotesAndAnswers;
return [
...latestAnswers,
...latestVotes.expand((it) => it),
...ownVotesAndAnswers,
];
}
}
24 changes: 24 additions & 0 deletions packages/stream_chat/test/src/db/chat_persistence_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:stream_chat/src/core/models/event.dart';
import 'package:stream_chat/src/core/models/filter.dart';
import 'package:stream_chat/src/core/models/member.dart';
import 'package:stream_chat/src/core/models/message.dart';
import 'package:stream_chat/src/core/models/poll.dart';
import 'package:stream_chat/src/core/models/poll_vote.dart';
import 'package:stream_chat/src/core/models/reaction.dart';
import 'package:stream_chat/src/core/models/read.dart';
import 'package:stream_chat/src/core/models/user.dart';
Expand Down Expand Up @@ -49,6 +51,9 @@ class TestPersistenceClient extends ChatPersistenceClient {
List<String> messageIds) =>
Future.value();

@override
Future<void> deletePollVotesByPollIds(List<String> pollIds) => Future.value();

@override
Future<void> disconnect({bool flush = false}) => throw UnimplementedError();

Expand Down Expand Up @@ -119,6 +124,9 @@ class TestPersistenceClient extends ChatPersistenceClient {
Future<void> updatePinnedMessageReactions(List<Reaction> reactions) =>
Future.value();

@override
Future<void> updatePollVotes(List<PollVote> pollVotes) => Future.value();

@override
Future<void> updateUsers(List<User> users) => Future.value();

Expand All @@ -137,6 +145,12 @@ class TestPersistenceClient extends ChatPersistenceClient {
@override
Future<void> bulkUpdateReads(Map<String, List<Read>?> reads) =>
Future.value();

@override
Future<void> deletePollsByIds(List<String> pollIds) => Future.value();

@override
Future<void> updatePolls(List<Poll> polls) => Future.value();
}

void main() {
Expand Down Expand Up @@ -169,6 +183,16 @@ void main() {
expect(channelState, isNotNull);
});

test('deletePollsByIds', () {
const pollIds = ['poll-id'];
persistenceClient.deletePollsByIds(pollIds);
});

test('updatePolls', () async {
final poll = Poll(id: 'poll-id', name: 'poll-name', options: const []);
persistenceClient.updatePolls([poll]);
});

test('updateChannelThreads', () async {
const cid = 'test:cid';
final user = User(id: 'test-user-id');
Expand Down
Loading

0 comments on commit fa7ae0f

Please sign in to comment.