[StreamChatPersistence] Add initial implementation
Signed-off-by: Sahil Kumar <xdsahil@gmail.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import 'package:stream_chat/src/api/requests.dart';
|
||||
import 'package:stream_chat/src/models/channel_model.dart';
|
||||
import 'package:stream_chat/src/models/channel_state.dart';
|
||||
import 'package:stream_chat/src/models/event.dart';
|
||||
import 'package:stream_chat/src/models/member.dart';
|
||||
import 'package:stream_chat/src/models/message.dart';
|
||||
import 'package:stream_chat/src/models/reaction.dart';
|
||||
import 'package:stream_chat/src/models/read.dart';
|
||||
import 'package:stream_chat/src/models/user.dart';
|
||||
|
||||
///
|
||||
abstract class StreamChatDatabase {
|
||||
/// Creates a new connection to the database
|
||||
Future<void> connect({
|
||||
bool connectBackground = false,
|
||||
bool logStatements = false,
|
||||
});
|
||||
|
||||
/// Closes the database instance
|
||||
/// If [flush] is true, the database data will be deleted
|
||||
Future<void> disconnect({bool flush = false});
|
||||
|
||||
/// Get stored replies by messageId
|
||||
Future<List<Message>> getReplies(
|
||||
String parentId, {
|
||||
String lessThan,
|
||||
});
|
||||
|
||||
/// Get stored connection event
|
||||
Future<Event> getConnectionInfo();
|
||||
|
||||
/// Get stored lastSyncAt
|
||||
Future<DateTime> getLastSyncAt();
|
||||
|
||||
/// Update stored connection event
|
||||
Future<void> updateConnectionInfo(Event event);
|
||||
|
||||
/// Update stored lastSyncAt
|
||||
Future<void> updateLastSyncAt(DateTime lastSyncAt);
|
||||
|
||||
/// Get the channel cids saved in the offline storage
|
||||
Future<List<String>> getChannelCids();
|
||||
|
||||
Future<ChannelModel> getChannelByCid(String cid);
|
||||
|
||||
Future<List<Member>> getMembersByCid(String cid);
|
||||
|
||||
Future<List<Read>> getReadsByCid(String cid);
|
||||
|
||||
Future<List<Message>> getMessagesByCid(
|
||||
String cid, {
|
||||
int limit = 20,
|
||||
String messageLessThan,
|
||||
String messageGreaterThan,
|
||||
});
|
||||
|
||||
/// Get list of channels by filter, sort and paginationParams
|
||||
Future<List<ChannelState>> getChannelStates({
|
||||
Map<String, dynamic> filter,
|
||||
List<SortOption> sort = const [],
|
||||
PaginationParams paginationParams,
|
||||
});
|
||||
|
||||
/// Update list of channel queries
|
||||
/// If [clearQueryCache] is true before the insert
|
||||
/// the list of matching rows will be deleted
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
bool clearQueryCache,
|
||||
);
|
||||
|
||||
/// Remove a message by message id
|
||||
Future<void> deleteMessageByIds(List<String> messageIds);
|
||||
|
||||
/// Remove a message by message id
|
||||
Future<void> deleteMessageByCids(List<String> cids);
|
||||
|
||||
/// Remove a channel by cid
|
||||
Future<void> deleteChannelByCids(List<String> cids);
|
||||
|
||||
/// Update messages data from a list
|
||||
Future<void> updateMessages(String cid, List<Message> messages);
|
||||
|
||||
/// Get the info about channel threads
|
||||
Future<Map<String, List<Message>>> getChannelThreads(String cid);
|
||||
|
||||
Future<void> updateChannels(List<ChannelModel> channels);
|
||||
|
||||
Future<void> updateMembers(String cid, List<Member> members);
|
||||
|
||||
Future<void> updateReads(String cid, List<Read> reads);
|
||||
|
||||
Future<void> updateUsers(List<User> users);
|
||||
|
||||
Future<void> updateReactions(List<Reaction> reactions);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../exceptions.dart';
|
||||
|
||||
part 'result.freezed.dart';
|
||||
|
||||
///
|
||||
@freezed
|
||||
abstract class Result<T> with _$Result<T> {
|
||||
///
|
||||
const factory Result.success({@required T data}) = Success<T>;
|
||||
|
||||
///
|
||||
const factory Result.error({@required ApiError error}) = Error<T>;
|
||||
}
|
||||
|
||||
///
|
||||
extension ResultX<T> on Result<T> {
|
||||
///
|
||||
bool get isSuccess => this is Success<T>;
|
||||
|
||||
///
|
||||
bool get isError => this is Error<T>;
|
||||
|
||||
///
|
||||
T get data {
|
||||
if (isError) {
|
||||
throw Exception(
|
||||
'Result is not successful. Check result.isSuccess before reading the data.',
|
||||
);
|
||||
}
|
||||
return (this as Success<T>).data;
|
||||
}
|
||||
|
||||
///
|
||||
ApiError get error {
|
||||
if (isSuccess) {
|
||||
throw Exception(
|
||||
'Result is successful, not an error. Check result.isError before reading the error.',
|
||||
);
|
||||
}
|
||||
return (this as Error<T>).error;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ library stream_chat;
|
||||
|
||||
export 'package:dio/src/dio_error.dart';
|
||||
export 'package:dio/src/multipart_file.dart';
|
||||
export 'package:logging/src/level.dart';
|
||||
export 'package:logging/logging.dart' show Logger, Level;
|
||||
|
||||
export './src/api/channel.dart';
|
||||
export './src/api/connection_status.dart';
|
||||
@@ -27,3 +27,5 @@ export './src/models/reaction.dart';
|
||||
export './src/models/read.dart';
|
||||
export './src/models/user.dart';
|
||||
export './src/notifications.dart';
|
||||
export './src/db/stream_chat_database.dart';
|
||||
export './src/utils/result.dart';
|
||||
|
||||
@@ -26,6 +26,9 @@ dependencies:
|
||||
collection: ^1.14.12
|
||||
sqlite3_flutter_libs: ^0.3.0
|
||||
pedantic: ^1.9.2
|
||||
freezed: ^0.12.7
|
||||
stream_chat_persistence:
|
||||
path: ../stream_chat_persistence
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^1.10.0
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
build/
|
||||
|
||||
# Android related
|
||||
**/android/**/gradle-wrapper.jar
|
||||
**/android/.gradle
|
||||
**/android/captures/
|
||||
**/android/gradlew
|
||||
**/android/gradlew.bat
|
||||
**/android/local.properties
|
||||
**/android/**/GeneratedPluginRegistrant.java
|
||||
|
||||
# iOS/XCode related
|
||||
**/ios/**/*.mode1v3
|
||||
**/ios/**/*.mode2v3
|
||||
**/ios/**/*.moved-aside
|
||||
**/ios/**/*.pbxuser
|
||||
**/ios/**/*.perspectivev3
|
||||
**/ios/**/*sync/
|
||||
**/ios/**/.sconsign.dblite
|
||||
**/ios/**/.tags*
|
||||
**/ios/**/.vagrant/
|
||||
**/ios/**/DerivedData/
|
||||
**/ios/**/Icon?
|
||||
**/ios/**/Pods/
|
||||
**/ios/**/.symlinks/
|
||||
**/ios/**/profile
|
||||
**/ios/**/xcuserdata
|
||||
**/ios/.generated/
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Flutter.podspec
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/app.flx
|
||||
**/ios/Flutter/app.zip
|
||||
**/ios/Flutter/flutter_assets/
|
||||
**/ios/Flutter/flutter_export_environment.sh
|
||||
**/ios/ServiceDefinitions.json
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!**/ios/**/default.mode1v3
|
||||
!**/ios/**/default.mode2v3
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 78910062997c3a836feee883712c241a5fd22983
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
||||
@@ -0,0 +1,3 @@
|
||||
## [0.0.1] - TODO: Add release date.
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
@@ -0,0 +1,14 @@
|
||||
# stream_chat_persistence
|
||||
|
||||
A new Flutter package.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Dart
|
||||
[package](https://flutter.dev/developing-packages/),
|
||||
a library module containing code that can be shared easily across
|
||||
multiple Flutter or Dart projects.
|
||||
|
||||
For help getting started with Flutter, view our
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@@ -0,0 +1,6 @@
|
||||
targets:
|
||||
$default:
|
||||
builders:
|
||||
moor_generator:
|
||||
options:
|
||||
generate_connect_constructor: true
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
///
|
||||
class ListConverter<T> extends TypeConverter<List<T>, String> {
|
||||
@override
|
||||
List<T> mapToDart(fromDb) {
|
||||
if (fromDb == null) {
|
||||
return null;
|
||||
}
|
||||
return List<T>.from(jsonDecode(fromDb) ?? []);
|
||||
}
|
||||
|
||||
@override
|
||||
String mapToSql(value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return jsonEncode(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
///
|
||||
class MapConverter<T> extends TypeConverter<Map<String, T>, String> {
|
||||
@override
|
||||
Map<String, T> mapToDart(fromDb) {
|
||||
if (fromDb == null) {
|
||||
return null;
|
||||
}
|
||||
return Map<String, T>.from(jsonDecode(fromDb) ?? {});
|
||||
}
|
||||
|
||||
@override
|
||||
String mapToSql(value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return jsonEncode(value);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
///
|
||||
class MessageSendingStatusConverter
|
||||
extends TypeConverter<MessageSendingStatus, int> {
|
||||
@override
|
||||
MessageSendingStatus mapToDart(int fromDb) {
|
||||
switch (fromDb) {
|
||||
case 0:
|
||||
return MessageSendingStatus.sending;
|
||||
case 1:
|
||||
return MessageSendingStatus.sent;
|
||||
case 2:
|
||||
return MessageSendingStatus.failed;
|
||||
case 3:
|
||||
return MessageSendingStatus.updating;
|
||||
case 4:
|
||||
return MessageSendingStatus.failed_update;
|
||||
case 5:
|
||||
return MessageSendingStatus.deleting;
|
||||
case 6:
|
||||
return MessageSendingStatus.failed_delete;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int mapToSql(MessageSendingStatus value) {
|
||||
switch (value) {
|
||||
case MessageSendingStatus.sending:
|
||||
return 0;
|
||||
case MessageSendingStatus.sent:
|
||||
return 1;
|
||||
case MessageSendingStatus.failed:
|
||||
return 2;
|
||||
case MessageSendingStatus.updating:
|
||||
return 3;
|
||||
case MessageSendingStatus.failed_update:
|
||||
return 4;
|
||||
case MessageSendingStatus.deleting:
|
||||
return 5;
|
||||
case MessageSendingStatus.failed_delete:
|
||||
return 6;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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/channels.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'channel_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [Channels, Users])
|
||||
class ChannelDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$ChannelDaoMixin {
|
||||
///
|
||||
ChannelDao(MoorChatDatabase db) : super(db);
|
||||
|
||||
/// Get channel by cid
|
||||
Future<ChannelModel> getChannelByCid(String cid) async {
|
||||
return (select(channels)..where((c) => c.cid.equals(cid))).join([
|
||||
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
||||
]).map((rows) {
|
||||
final channel = rows.readTable(channels);
|
||||
final createdBy = rows.readTable(users);
|
||||
return channel.toChannelModel(createdBy: createdBy?.toUser());
|
||||
}).getSingle();
|
||||
}
|
||||
|
||||
/// Delete all channels by matching cid in [cids]
|
||||
///
|
||||
/// This will automatically delete the following linked records
|
||||
/// 1. Channel Reads
|
||||
/// 2. Channel Members
|
||||
/// 3. Channel Messages -> Messages Reactions
|
||||
Future<void> deleteChannelByCids(List<String> cids) async {
|
||||
return (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go();
|
||||
}
|
||||
|
||||
/// Get the channel cids saved in the storage
|
||||
Future<List<String>> get cids {
|
||||
return (select(channels)
|
||||
..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)])
|
||||
..limit(250))
|
||||
.map((c) => c.cid)
|
||||
.get();
|
||||
}
|
||||
|
||||
///
|
||||
Future<void> updateChannels(List<ChannelModel> channelList) {
|
||||
return batch(
|
||||
(it) => it.insertAll(
|
||||
channels,
|
||||
channelList.map((c) => c.toEntity()).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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/channel_queries.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/channels.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'channel_query_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [ChannelQueries, Channels, Users])
|
||||
class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$ChannelQueryDaoMixin {
|
||||
///
|
||||
ChannelQueryDao(this._db) : super(_db);
|
||||
|
||||
final MoorChatDatabase _db;
|
||||
|
||||
String _computeHash(Map<String, dynamic> filter) {
|
||||
if (filter == null) {
|
||||
return 'allchannels';
|
||||
}
|
||||
final hash = base64Encode(utf8.encode('filter: ${jsonEncode(filter)}'));
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// Update list of channel queries
|
||||
/// If [clearQueryCache] is true before the insert
|
||||
/// the list of matching rows will be deleted
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
bool clearQueryCache,
|
||||
) async {
|
||||
final hash = _computeHash(filter);
|
||||
if (clearQueryCache) {
|
||||
await (delete(channelQueries)
|
||||
..where((query) => query.queryHash.equals(hash)))
|
||||
.go();
|
||||
}
|
||||
|
||||
return batch((batch) {
|
||||
batch.insertAll(
|
||||
channelQueries,
|
||||
cids.map((cid) {
|
||||
return ChannelQueryEntity(
|
||||
queryHash: hash,
|
||||
channelCid: cid,
|
||||
);
|
||||
}).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Get list of channels by filter, sort and paginationParams
|
||||
Future<List<ChannelState>> getChannelStates({
|
||||
Map<String, dynamic> filter,
|
||||
List<SortOption> sort = const [],
|
||||
PaginationParams paginationParams,
|
||||
}) async {
|
||||
final hash = _computeHash(filter);
|
||||
final cachedChannels = await Future.wait(await (select(channelQueries)
|
||||
..where((c) => c.queryHash.equals(hash)))
|
||||
.get()
|
||||
.then((channelQueries) {
|
||||
final cids = channelQueries.map((c) => c.channelCid).toList();
|
||||
final query = select(channels)..where((c) => c.cid.isIn(cids));
|
||||
|
||||
sort = sort
|
||||
?.where((s) => ChannelModel.topLevelFields.contains(s.field))
|
||||
?.toList();
|
||||
|
||||
if (sort != null && sort.isNotEmpty) {
|
||||
query.orderBy(sort.map((s) {
|
||||
final orderExpression = CustomExpression('channels.${s.field}');
|
||||
return (c) => OrderingTerm(
|
||||
expression: orderExpression,
|
||||
mode: s.direction == 1 ? OrderingMode.asc : OrderingMode.desc,
|
||||
);
|
||||
}).toList());
|
||||
}
|
||||
|
||||
if (paginationParams != null) {
|
||||
query.limit(
|
||||
paginationParams.limit ?? 10,
|
||||
offset: paginationParams.offset,
|
||||
);
|
||||
}
|
||||
|
||||
return query.join([
|
||||
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
|
||||
]).map((row) async {
|
||||
final userEntity = row.readTable(users);
|
||||
final channelEntity = row.readTable(channels);
|
||||
|
||||
final cid = channelEntity.cid;
|
||||
final members = await _db.memberDao.getMembersByCid(cid);
|
||||
final reads = await _db.readDao.getReadsByCid(cid);
|
||||
final messages = await _db.messageDao.getMessagesByCid(cid);
|
||||
|
||||
return channelEntity.toChannelState(
|
||||
createdBy: userEntity?.toUser(),
|
||||
members: members,
|
||||
reads: reads,
|
||||
messages: messages,
|
||||
);
|
||||
}).get();
|
||||
}));
|
||||
|
||||
if (sort?.isEmpty != false && cachedChannels?.isNotEmpty == true) {
|
||||
cachedChannels
|
||||
.sort((a, b) => b.channel.updatedAt.compareTo(a.channel.updatedAt));
|
||||
cachedChannels.sort((a, b) {
|
||||
final dateA = a.channel.lastMessageAt ?? a.channel.createdAt;
|
||||
final dateB = b.channel.lastMessageAt ?? b.channel.createdAt;
|
||||
return dateB.compareTo(dateA);
|
||||
});
|
||||
}
|
||||
|
||||
return cachedChannels;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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/connection_events.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'connection_event_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [ConnectionEvents, Users])
|
||||
class ConnectionEventDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$ConnectionEventDaoMixin {
|
||||
///
|
||||
ConnectionEventDao(MoorChatDatabase db) : super(db);
|
||||
|
||||
/// Get the latest stored connection event
|
||||
Future<Event> get connectionEvent {
|
||||
return select(connectionEvents).join([
|
||||
leftOuterJoin(users, connectionEvents.ownUserId.equalsExp(users.id)),
|
||||
]).map((rows) {
|
||||
final event = rows.readTable(connectionEvents);
|
||||
final user = rows.readTable(users);
|
||||
return event.toEvent(user: user?.toUser());
|
||||
}).getSingle();
|
||||
}
|
||||
|
||||
/// Get the latest stored lastSyncAt
|
||||
Future<DateTime> get lastSyncAt {
|
||||
return select(connectionEvents).getSingle().then((r) => r?.lastSyncAt);
|
||||
}
|
||||
|
||||
/// Update stored connection event with latest data
|
||||
Future<int> updateConnectionEvent(Event event) async {
|
||||
final connectionInfo = await select(connectionEvents).getSingle();
|
||||
return into(connectionEvents).insert(
|
||||
ConnectionEventEntity(
|
||||
id: 1,
|
||||
lastSyncAt: connectionInfo?.lastSyncAt,
|
||||
lastEventAt: event.createdAt ?? connectionInfo?.lastEventAt,
|
||||
totalUnreadCount:
|
||||
event.totalUnreadCount ?? connectionInfo?.totalUnreadCount,
|
||||
ownUserId: event.me?.id ?? connectionInfo?.ownUserId,
|
||||
unreadChannels: event.unreadChannels ?? connectionInfo?.unreadChannels,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update stored lastSyncAt with latest data
|
||||
Future<int> updateLastSyncAt(DateTime lastSyncAt) async {
|
||||
return (update(connectionEvents)..where((tbl) => tbl.id.equals(1))).write(
|
||||
ConnectionEventsCompanion(
|
||||
lastSyncAt: Value(lastSyncAt),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export 'user_dao.dart';
|
||||
export 'channel_dao.dart';
|
||||
export 'message_dao.dart';
|
||||
export 'member_dao.dart';
|
||||
export 'connection_event_dao.dart';
|
||||
export 'reaction_dao.dart';
|
||||
export 'read_dao.dart';
|
||||
export 'channel_query_dao.dart';
|
||||
@@ -0,0 +1,43 @@
|
||||
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/members.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'member_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [Members, Users])
|
||||
class MemberDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$MemberDaoMixin {
|
||||
///
|
||||
MemberDao(MoorChatDatabase db) : super(db);
|
||||
|
||||
/// Get all members where [members.channelCid] matches [cid]
|
||||
Future<List<Member>> getMembersByCid(String cid) async {
|
||||
return (select(members).join([
|
||||
leftOuterJoin(users, members.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(members.channelCid.equals(cid))
|
||||
..orderBy([OrderingTerm.asc(members.createdAt)]))
|
||||
.map((row) {
|
||||
final userEntity = row.readTable(users);
|
||||
final memberEntity = row.readTable(members);
|
||||
return memberEntity.toMember(user: userEntity?.toUser());
|
||||
}).get();
|
||||
}
|
||||
|
||||
///
|
||||
Future<void> updateMembers(String cid, List<Member> memberList) async {
|
||||
return batch(
|
||||
(it) => it.insertAll(
|
||||
members,
|
||||
memberList.map((m) => m.toEntity(cid: cid)).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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/messages.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'message_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [Messages, Users])
|
||||
class MessageDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$MessageDaoMixin {
|
||||
///
|
||||
MessageDao(this._db) : super(_db);
|
||||
|
||||
final MoorChatDatabase _db;
|
||||
|
||||
/// Removes all the messages by matching [messages.id] in [messageIds]
|
||||
///
|
||||
/// This will automatically delete the following linked records
|
||||
/// 1. Message Reactions
|
||||
Future<void> deleteMessageByIds(List<String> messageIds) {
|
||||
return (delete(messages)..where((tbl) => tbl.id.isIn(messageIds))).go();
|
||||
}
|
||||
|
||||
/// Removes all the messages by matching [messages.channelCid] in [cids]
|
||||
///
|
||||
/// This will automatically delete the following linked records
|
||||
/// 1. Message Reactions
|
||||
Future<void> deleteMessageByCids(List<String> cids) async {
|
||||
return (delete(messages)..where((tbl) => tbl.channelCid.isIn(cids))).go();
|
||||
}
|
||||
|
||||
Future<Message> _messageFromJoinRow(TypedResult rows) async {
|
||||
final userEntity = rows.readTable(users);
|
||||
final msgEntity = rows.readTable(messages);
|
||||
final latestReactions = await _db.reactionDao.getReactions(msgEntity.id);
|
||||
final ownReactions = await _db.reactionDao.getOwnReactions(
|
||||
msgEntity.id,
|
||||
userEntity.id,
|
||||
);
|
||||
Message quotedMessage;
|
||||
if (msgEntity.quotedMessageId != null) {
|
||||
quotedMessage = await getMessageById(msgEntity.quotedMessageId);
|
||||
}
|
||||
return msgEntity.toMessage(
|
||||
user: userEntity?.toUser(),
|
||||
latestReactions: latestReactions,
|
||||
ownReactions: ownReactions,
|
||||
quotedMessage: quotedMessage,
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
Future<Message> getMessageById(String id) async {
|
||||
return await (select(messages).join([
|
||||
leftOuterJoin(users, messages.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(messages.id.equals(id)))
|
||||
.map(_messageFromJoinRow)
|
||||
.getSingle();
|
||||
}
|
||||
|
||||
///
|
||||
Future<List<Message>> getThreadMessages(String cid) async {
|
||||
return Future.wait(await (select(messages).join([
|
||||
leftOuterJoin(users, messages.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(messages.channelCid.equals(cid))
|
||||
..where(isNotNull(messages.parentId))
|
||||
..orderBy([OrderingTerm.asc(messages.createdAt)]))
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
}
|
||||
|
||||
///
|
||||
Future<List<Message>> getThreadMessagesByParentId(
|
||||
String parentId, {
|
||||
String lessThan,
|
||||
}) async {
|
||||
final msgList = await Future.wait(await (select(messages).join([
|
||||
innerJoin(users, messages.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(messages.parentId.equals(parentId))
|
||||
..orderBy([OrderingTerm.asc(messages.createdAt)]))
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
|
||||
if (lessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere((m) => m.id == lessThan);
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
|
||||
///
|
||||
Future<List<Message>> getMessagesByCid(
|
||||
String cid, {
|
||||
int limit = 20,
|
||||
String messageLessThan,
|
||||
String messageGreaterThan,
|
||||
}) async {
|
||||
final msgList = await Future.wait(await (select(messages).join([
|
||||
leftOuterJoin(users, messages.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(messages.channelCid.equals(cid))
|
||||
..where(
|
||||
isNull(messages.parentId) | messages.showInChannel.equals(true))
|
||||
..orderBy([OrderingTerm.asc(messages.createdAt)]))
|
||||
.map(_messageFromJoinRow)
|
||||
.get());
|
||||
|
||||
if (messageLessThan != null) {
|
||||
final lessThanIndex = msgList.indexWhere((m) => m.id == messageLessThan);
|
||||
if (lessThanIndex != -1) {
|
||||
msgList.removeRange(lessThanIndex, msgList.length);
|
||||
}
|
||||
}
|
||||
if (messageGreaterThan != null) {
|
||||
final greaterThanIndex =
|
||||
msgList.indexWhere((m) => m.id == messageGreaterThan);
|
||||
if (greaterThanIndex != -1) {
|
||||
msgList.removeRange(0, greaterThanIndex);
|
||||
}
|
||||
}
|
||||
if (limit != null) {
|
||||
return msgList.take(limit).toList();
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
|
||||
///
|
||||
Future<int> updateMessages(String cid, List<Message> messageList) {
|
||||
return batch((batch) {
|
||||
batch.insertAll(
|
||||
messages,
|
||||
messageList.map((it) => it.toEntity(cid: cid)).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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/reactions.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'reaction_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [Reactions, Users])
|
||||
class ReactionDao extends DatabaseAccessor<MoorChatDatabase>
|
||||
with _$ReactionDaoMixin {
|
||||
///
|
||||
ReactionDao(MoorChatDatabase db) : super(db);
|
||||
|
||||
///
|
||||
Future<List<Reaction>> getReactions(String messageId) {
|
||||
return (select(reactions).join([
|
||||
leftOuterJoin(users, reactions.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(reactions.messageId.equals(messageId))
|
||||
..orderBy([OrderingTerm.asc(reactions.createdAt)]))
|
||||
.map((rows) {
|
||||
final userEntity = rows.readTable(users);
|
||||
final reactionEntity = rows.readTable(reactions);
|
||||
return reactionEntity.toReaction(user: userEntity?.toUser());
|
||||
}).get();
|
||||
}
|
||||
|
||||
///
|
||||
Future<List<Reaction>> getOwnReactions(
|
||||
String messageId,
|
||||
String userId,
|
||||
) async {
|
||||
final reactions = await getReactions(messageId);
|
||||
return reactions.where((it) => it.userId == userId).toList();
|
||||
}
|
||||
|
||||
///
|
||||
Future<void> updateReactions(List<Reaction> reactionList) {
|
||||
return batch((it) {
|
||||
it.insertAll(
|
||||
reactions,
|
||||
reactionList.map((r) => r.toEntity()).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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/reads.dart';
|
||||
import 'package:stream_chat_persistence/src/entity/users.dart';
|
||||
import '../mapper/mapper.dart';
|
||||
|
||||
part 'read_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [Reads, Users])
|
||||
class ReadDao extends DatabaseAccessor<MoorChatDatabase> with _$ReadDaoMixin {
|
||||
///
|
||||
ReadDao(MoorChatDatabase db) : super(db);
|
||||
|
||||
/// Get all reads where [reads.channelCid] matches [cid]
|
||||
Future<List<Read>> getReadsByCid(String cid) async {
|
||||
return (select(reads).join([
|
||||
leftOuterJoin(users, reads.userId.equalsExp(users.id)),
|
||||
])
|
||||
..where(reads.channelCid.equals(cid))
|
||||
..orderBy([
|
||||
OrderingTerm.asc(reads.lastRead),
|
||||
]))
|
||||
.map((row) {
|
||||
final userEntity = row.readTable(users);
|
||||
final readEntity = row.readTable(reads);
|
||||
return readEntity.toRead(user: userEntity?.toUser());
|
||||
}).get();
|
||||
}
|
||||
|
||||
///
|
||||
Future<void> updateReads(String cid, List<Read> readList) {
|
||||
return batch(
|
||||
(it) => it.insertAll(
|
||||
reads,
|
||||
readList.map((r) => r.toEntity(cid: cid)).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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/users.dart';
|
||||
import '../mapper/user_mapper.dart';
|
||||
|
||||
part 'user_dao.g.dart';
|
||||
|
||||
///
|
||||
@UseDao(tables: [Users])
|
||||
class UserDao extends DatabaseAccessor<MoorChatDatabase> with _$UserDaoMixin {
|
||||
///
|
||||
UserDao(MoorChatDatabase db) : super(db);
|
||||
|
||||
///
|
||||
Future<void> updateUsers(List<User> userList) {
|
||||
return batch(
|
||||
(it) => it.insertAll(
|
||||
users,
|
||||
userList.map((u) => u.toEntity()).toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
import '../entity/entity.dart';
|
||||
import '../dao/dao.dart';
|
||||
import 'shared/shared_db.dart';
|
||||
|
||||
part 'moor_chat_database.g.dart';
|
||||
|
||||
LazyDatabase _openConnection(
|
||||
String dbName, {
|
||||
logStatements = false,
|
||||
}) {
|
||||
return LazyDatabase(() async {
|
||||
return await SharedDB.constructDatabase(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
@UseMoor(tables: [
|
||||
Channels,
|
||||
Messages,
|
||||
Reactions,
|
||||
Users,
|
||||
Members,
|
||||
Reads,
|
||||
ChannelQueries,
|
||||
ConnectionEvents,
|
||||
], daos: [
|
||||
UserDao,
|
||||
ChannelDao,
|
||||
MessageDao,
|
||||
MemberDao,
|
||||
ReactionDao,
|
||||
ReadDao,
|
||||
ChannelQueryDao,
|
||||
ConnectionEventDao,
|
||||
])
|
||||
class MoorChatDatabase extends _$MoorChatDatabase {
|
||||
/// Instantiate a new database instance
|
||||
MoorChatDatabase(
|
||||
String dbName, {
|
||||
logStatements = false,
|
||||
}) : super(_openConnection(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
));
|
||||
|
||||
/// Instantiate a new database instance
|
||||
MoorChatDatabase.connect(
|
||||
this._isolate,
|
||||
DatabaseConnection connection,
|
||||
) : super.connect(connection);
|
||||
|
||||
MoorIsolate _isolate;
|
||||
|
||||
// you should bump this number whenever you change or add a table definition.
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onUpgrade: (openingDetails, before, after) async {
|
||||
if (before != after) {
|
||||
final m = createMigrator();
|
||||
for (final table in allTables) {
|
||||
await m.deleteTable(table.actualTableName);
|
||||
await m.createTable(table);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/// Closes the database instance
|
||||
Future<void> disconnect() async {
|
||||
await _isolate?.shutdownAll();
|
||||
await close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/isolate.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../moor_chat_database.dart';
|
||||
|
||||
class SharedDB {
|
||||
static Future<VmDatabase> constructDatabase(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = join(dir.path, '$dbName.sqlite');
|
||||
final file = File(path);
|
||||
return VmDatabase(file, logStatements: logStatements);
|
||||
}
|
||||
if (Platform.isMacOS || Platform.isLinux) {
|
||||
final file = File('$dbName.sqlite');
|
||||
return VmDatabase(file, logStatements: logStatements);
|
||||
}
|
||||
return VmDatabase.memory(logStatements: logStatements);
|
||||
}
|
||||
|
||||
static void _startBackground(_IsolateStartRequest request) {
|
||||
final executor = LazyDatabase(() async {
|
||||
return VmDatabase(
|
||||
File(request.targetPath),
|
||||
logStatements: request.logStatements,
|
||||
);
|
||||
});
|
||||
final moorIsolate = MoorIsolate.inCurrent(
|
||||
() => DatabaseConnection.fromExecutor(executor),
|
||||
);
|
||||
request.sendMoorIsolate.send(moorIsolate);
|
||||
}
|
||||
|
||||
static Future<MoorIsolate> _createMoorIsolate(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = join(dir.path, '$dbName.sqlite');
|
||||
|
||||
final receivePort = ReceivePort();
|
||||
await Isolate.spawn(
|
||||
_startBackground,
|
||||
_IsolateStartRequest(
|
||||
receivePort.sendPort,
|
||||
path,
|
||||
logStatements: logStatements,
|
||||
),
|
||||
);
|
||||
|
||||
return (await receivePort.first as MoorIsolate);
|
||||
}
|
||||
|
||||
static Future<MoorChatDatabase> constructOfflineStorage(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
final isolate = await _createMoorIsolate(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
);
|
||||
final connection = await isolate.connect();
|
||||
return MoorChatDatabase.connect(isolate, connection);
|
||||
}
|
||||
}
|
||||
|
||||
class _IsolateStartRequest {
|
||||
final SendPort sendMoorIsolate;
|
||||
final String targetPath;
|
||||
final bool logStatements;
|
||||
|
||||
const _IsolateStartRequest(
|
||||
this.sendMoorIsolate,
|
||||
this.targetPath, {
|
||||
this.logStatements = false,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export 'unsupported_db.dart'
|
||||
if (dart.library.io) 'native_db.dart' // implementation using dart:io
|
||||
if (dart.library.html) 'web_db.dart';
|
||||
@@ -0,0 +1,18 @@
|
||||
//ignore_for_file: public_member_api_docs
|
||||
//ignore_for_file: always_declare_return_types
|
||||
|
||||
class SharedDB {
|
||||
static constructDatabase(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) {
|
||||
throw 'Unsupported Platform';
|
||||
}
|
||||
|
||||
static constructOfflineStorage(
|
||||
String dbName, {
|
||||
logStatements = false,
|
||||
}) {
|
||||
throw 'Unsupported Platform';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//ignore_for_file: public_member_api_docs
|
||||
//ignore_for_file: always_declare_return_types
|
||||
import 'package:moor/moor_web.dart';
|
||||
|
||||
import '../moor_chat_database.dart';
|
||||
|
||||
class SharedDB {
|
||||
static constructDatabase(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
return WebDatabase(dbName, logStatements: logStatements);
|
||||
}
|
||||
|
||||
static Future<MoorChatDatabase> constructOfflineStorage(
|
||||
String dbName, {
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
return MoorChatDatabase(dbName, logStatements: logStatements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
import 'moor_chat_database.dart';
|
||||
import 'shared/shared_db.dart';
|
||||
|
||||
///
|
||||
class StreamChatDatabaseImpl implements StreamChatDatabase {
|
||||
///
|
||||
StreamChatDatabaseImpl(
|
||||
this._userId, {
|
||||
Logger logger,
|
||||
}) : _logger = logger,
|
||||
assert(_userId != null);
|
||||
|
||||
final String _userId;
|
||||
final Logger _logger;
|
||||
MoorChatDatabase _db;
|
||||
|
||||
bool get _debugAssertConnected {
|
||||
assert(() {
|
||||
if (_db == null) {
|
||||
throw Exception(
|
||||
'A $runtimeType was used after being disconnected.\n'
|
||||
'Once you have called disconnect() on a $runtimeType, it can no longer be used.',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> connect({
|
||||
bool connectBackground = false,
|
||||
bool logStatements = false,
|
||||
}) async {
|
||||
if (_db != null) {
|
||||
throw Exception(
|
||||
'An instance of StreamChatDatabase is already connected.\n'
|
||||
'disconnect the previous instance before connecting again.',
|
||||
);
|
||||
}
|
||||
|
||||
final dbName = 'db_$_userId';
|
||||
if (connectBackground) {
|
||||
_logger?.info('Connecting on background isolate');
|
||||
_db = await SharedDB.constructOfflineStorage(
|
||||
dbName,
|
||||
logStatements: logStatements,
|
||||
);
|
||||
} else {
|
||||
_logger?.info('Connecting on a regular isolate');
|
||||
_db = MoorChatDatabase(dbName, logStatements: logStatements);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Event> getConnectionInfo() {
|
||||
return _db.connectionEventDao.connectionEvent;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateConnectionInfo(Event event) {
|
||||
return _db.connectionEventDao.updateConnectionEvent(event);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateLastSyncAt(DateTime lastSyncAt) {
|
||||
return _db.connectionEventDao.updateLastSyncAt(lastSyncAt);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DateTime> getLastSyncAt() {
|
||||
return _db.connectionEventDao.lastSyncAt;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteChannelByCids(List<String> cids) {
|
||||
return _db.channelDao.deleteChannelByCids(cids);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getChannelCids() => _db.channelDao.cids;
|
||||
|
||||
@override
|
||||
Future<void> deleteMessageByIds(List<String> messageIds) {
|
||||
return _db.messageDao.deleteMessageByIds(messageIds);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteMessageByCids(List<String> cids) {
|
||||
return _db.messageDao.deleteMessageByCids(cids);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Member>> getMembersByCid(String cid) {
|
||||
return _db.memberDao.getMembersByCid(cid);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ChannelModel> getChannelByCid(String cid) {
|
||||
return _db.channelDao.getChannelByCid(cid);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Message>> getMessagesByCid(
|
||||
String cid, {
|
||||
int limit = 20,
|
||||
String messageLessThan,
|
||||
String messageGreaterThan,
|
||||
}) {
|
||||
return _db.messageDao.getMessagesByCid(
|
||||
cid,
|
||||
limit: limit,
|
||||
messageLessThan: messageLessThan,
|
||||
messageGreaterThan: messageGreaterThan,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Read>> getReadsByCid(String cid) {
|
||||
return _db.readDao.getReadsByCid(cid);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List<Message>>> getChannelThreads(String cid) async {
|
||||
final messages = await _db.messageDao.getThreadMessages(cid);
|
||||
final messageByParentIdDictionary = <String, List<Message>>{};
|
||||
for (final message in messages) {
|
||||
final parentId = message.parentId;
|
||||
messageByParentIdDictionary[parentId] = [
|
||||
...messageByParentIdDictionary[parentId] ?? [],
|
||||
message
|
||||
];
|
||||
}
|
||||
return messageByParentIdDictionary;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Message>> getReplies(
|
||||
String parentId, {
|
||||
String lessThan,
|
||||
}) {
|
||||
return _db.messageDao.getThreadMessagesByParentId(
|
||||
parentId,
|
||||
lessThan: lessThan,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ChannelState>> getChannelStates({
|
||||
Map<String, dynamic> filter,
|
||||
List<SortOption> sort = const [],
|
||||
PaginationParams paginationParams,
|
||||
}) {
|
||||
return _db.channelQueryDao.getChannelStates(
|
||||
filter: filter,
|
||||
sort: sort,
|
||||
paginationParams: paginationParams,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateChannelQueries(
|
||||
Map<String, dynamic> filter,
|
||||
List<String> cids,
|
||||
bool clearQueryCache,
|
||||
) {
|
||||
return _db.channelQueryDao.updateChannelQueries(
|
||||
filter,
|
||||
cids,
|
||||
clearQueryCache,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateChannels(List<ChannelModel> channels) {
|
||||
return _db.channelDao.updateChannels(channels);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateMembers(String cid, List<Member> members) {
|
||||
return _db.memberDao.updateMembers(cid, members);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateMessages(String cid, List<Message> messages) {
|
||||
return _db.messageDao.updateMessages(cid, messages);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateReactions(List<Reaction> reactions) {
|
||||
return _db.reactionDao.updateReactions(reactions);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateReads(String cid, List<Read> reads) {
|
||||
return _db.readDao.updateReads(cid, reads);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateUsers(List<User> users) {
|
||||
return _db.userDao.updateUsers(users);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> disconnect({bool flush = false}) async {
|
||||
if (_db != null) {
|
||||
_logger?.info('Disconnecting');
|
||||
if (flush) {
|
||||
_logger?.info('Flushing');
|
||||
await _db.batch((batch) {
|
||||
_db.allTables.forEach((table) {
|
||||
_db.delete(table).go();
|
||||
});
|
||||
});
|
||||
}
|
||||
await _db.disconnect();
|
||||
_db = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
@DataClassName('ChannelQueryEntity')
|
||||
class ChannelQueries extends Table {
|
||||
TextColumn get queryHash => text()();
|
||||
|
||||
TextColumn get channelCid => text()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {
|
||||
queryHash,
|
||||
channelCid,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
@DataClassName('ChannelEntity')
|
||||
class Channels extends Table {
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get type => text()();
|
||||
|
||||
TextColumn get cid => text()();
|
||||
|
||||
TextColumn get config => text().map(MapConverter<Object>())();
|
||||
|
||||
BoolColumn get frozen => boolean().withDefault(Constant(false))();
|
||||
|
||||
DateTimeColumn get lastMessageAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||
|
||||
IntColumn get memberCount => integer().nullable()();
|
||||
|
||||
TextColumn get createdById => text().nullable()();
|
||||
|
||||
TextColumn get extraData => text().nullable().map(MapConverter<Object>())();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {cid};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
@DataClassName('ConnectionEventEntity')
|
||||
class ConnectionEvents extends Table {
|
||||
IntColumn get id => integer()();
|
||||
|
||||
TextColumn get ownUserId => text().nullable()();
|
||||
|
||||
IntColumn get totalUnreadCount => integer().nullable()();
|
||||
|
||||
IntColumn get unreadChannels => integer().nullable()();
|
||||
|
||||
DateTimeColumn get lastEventAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get lastSyncAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export 'channels.dart';
|
||||
export 'messages.dart';
|
||||
export 'reactions.dart';
|
||||
export 'users.dart';
|
||||
export 'members.dart';
|
||||
export 'reads.dart';
|
||||
export 'channel_queries.dart';
|
||||
export 'connection_events.dart';
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
@DataClassName('MemberEntity')
|
||||
class Members extends Table {
|
||||
TextColumn get userId => text()();
|
||||
|
||||
TextColumn get channelCid =>
|
||||
text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')();
|
||||
|
||||
TextColumn get role => text().nullable()();
|
||||
|
||||
DateTimeColumn get inviteAcceptedAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get inviteRejectedAt => dateTime().nullable()();
|
||||
|
||||
BoolColumn get invited => boolean().nullable()();
|
||||
|
||||
BoolColumn get banned => boolean().nullable()();
|
||||
|
||||
BoolColumn get shadowBanned => boolean().nullable()();
|
||||
|
||||
BoolColumn get isModerator => boolean().nullable()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {
|
||||
userId,
|
||||
channelCid,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/list_converter.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart';
|
||||
|
||||
@DataClassName('MessageEntity')
|
||||
class Messages extends Table {
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get messageText => text().nullable()();
|
||||
|
||||
TextColumn get attachments =>
|
||||
text().nullable().map(ListConverter<String>())();
|
||||
|
||||
IntColumn get status =>
|
||||
integer().nullable().map(MessageSendingStatusConverter())();
|
||||
|
||||
TextColumn get type => text().nullable()();
|
||||
|
||||
TextColumn get mentionedUsers =>
|
||||
text().nullable().map(ListConverter<String>())();
|
||||
|
||||
TextColumn get reactionCounts => text().nullable().map(MapConverter<int>())();
|
||||
|
||||
TextColumn get reactionScores => text().nullable().map(MapConverter<int>())();
|
||||
|
||||
TextColumn get parentId => text().nullable()();
|
||||
|
||||
TextColumn get quotedMessageId => text().nullable()();
|
||||
|
||||
IntColumn get replyCount => integer().nullable()();
|
||||
|
||||
BoolColumn get showInChannel => boolean().nullable()();
|
||||
|
||||
BoolColumn get shadowed => boolean().nullable()();
|
||||
|
||||
TextColumn get command => text().nullable()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||
|
||||
TextColumn get userId => text().nullable()();
|
||||
|
||||
TextColumn get channelCid => text().nullable().customConstraint(
|
||||
'NULLABLE REFERENCES channels(cid) ON DELETE CASCADE')();
|
||||
|
||||
TextColumn get extraData => text().nullable().map(MapConverter<Object>())();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
@DataClassName('ReactionEntity')
|
||||
class Reactions extends Table {
|
||||
TextColumn get userId => text()();
|
||||
|
||||
TextColumn get messageId =>
|
||||
text().customConstraint('REFERENCES messages(id) ON DELETE CASCADE')();
|
||||
|
||||
TextColumn get type => text()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
IntColumn get score => integer().nullable()();
|
||||
|
||||
TextColumn get extraData => text().nullable().map(MapConverter<Object>())();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {
|
||||
messageId,
|
||||
type,
|
||||
userId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
@DataClassName('ReadEntity')
|
||||
class Reads extends Table {
|
||||
DateTimeColumn get lastRead => dateTime()();
|
||||
|
||||
TextColumn get userId => text()();
|
||||
|
||||
TextColumn get channelCid =>
|
||||
text().customConstraint('REFERENCES channels(cid) ON DELETE CASCADE')();
|
||||
|
||||
IntColumn get unreadMessages => integer().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {
|
||||
userId,
|
||||
channelCid,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:stream_chat_persistence/src/converter/map_converter.dart';
|
||||
|
||||
@DataClassName('UserEntity')
|
||||
class Users extends Table {
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get role => text().nullable()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get updatedAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get lastActive => dateTime().nullable()();
|
||||
|
||||
BoolColumn get online => boolean().nullable()();
|
||||
|
||||
BoolColumn get banned => boolean().nullable()();
|
||||
|
||||
TextColumn get extraData => text().nullable().map(MapConverter<Object>())();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'user_mapper.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
///
|
||||
extension ChannelEntityX on ChannelEntity {
|
||||
///
|
||||
ChannelModel toChannelModel({User createdBy}) {
|
||||
final config = ChannelConfig.fromJson(this.config ?? {});
|
||||
return ChannelModel(
|
||||
id: id,
|
||||
config: config,
|
||||
type: type,
|
||||
frozen: frozen,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
memberCount: memberCount,
|
||||
cid: cid,
|
||||
lastMessageAt: lastMessageAt,
|
||||
deletedAt: deletedAt,
|
||||
extraData: extraData,
|
||||
createdBy: createdBy,
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
ChannelState toChannelState({
|
||||
User createdBy,
|
||||
List<Member> members,
|
||||
List<Read> reads,
|
||||
List<Message> messages,
|
||||
}) {
|
||||
return ChannelState(
|
||||
members: members,
|
||||
read: reads,
|
||||
messages: messages,
|
||||
channel: toChannelModel(createdBy: createdBy),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension ChannelModelX on ChannelModel {
|
||||
///
|
||||
ChannelEntity toEntity() {
|
||||
return ChannelEntity(
|
||||
id: id,
|
||||
type: type,
|
||||
cid: cid,
|
||||
config: config.toJson(),
|
||||
frozen: frozen,
|
||||
lastMessageAt: lastMessageAt,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
deletedAt: deletedAt,
|
||||
memberCount: memberCount,
|
||||
createdById: createdBy.id,
|
||||
extraData: extraData,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'user_mapper.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
///
|
||||
extension ConnectionEventX on ConnectionEventEntity {
|
||||
///
|
||||
Event toEvent({User user}) {
|
||||
return Event(
|
||||
me: user,
|
||||
totalUnreadCount: totalUnreadCount,
|
||||
unreadChannels: unreadChannels,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export 'user_mapper.dart';
|
||||
export 'reaction_mapper.dart';
|
||||
export 'channel_mapper.dart';
|
||||
export 'event_mapper.dart';
|
||||
export 'member_mapper.dart';
|
||||
export 'read_mapper.dart';
|
||||
export 'message_mapper.dart';
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'user_mapper.dart';
|
||||
|
||||
///
|
||||
extension MemberEntityX on MemberEntity {
|
||||
///
|
||||
Member toMember({User user}) {
|
||||
return Member(
|
||||
user: user,
|
||||
userId: userId,
|
||||
banned: banned,
|
||||
shadowBanned: shadowBanned,
|
||||
updatedAt: updatedAt,
|
||||
createdAt: createdAt,
|
||||
role: role,
|
||||
inviteAcceptedAt: inviteAcceptedAt,
|
||||
invited: invited,
|
||||
inviteRejectedAt: inviteRejectedAt,
|
||||
isModerator: isModerator,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension MemberX on Member {
|
||||
///
|
||||
MemberEntity toEntity({String cid}) {
|
||||
return MemberEntity(
|
||||
userId: user?.id,
|
||||
banned: banned,
|
||||
shadowBanned: shadowBanned,
|
||||
channelCid: cid,
|
||||
createdAt: createdAt,
|
||||
isModerator: isModerator,
|
||||
inviteRejectedAt: inviteRejectedAt,
|
||||
invited: invited,
|
||||
inviteAcceptedAt: inviteAcceptedAt,
|
||||
role: role,
|
||||
updatedAt: updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
///
|
||||
extension MessageEntityX on MessageEntity {
|
||||
///
|
||||
Message toMessage({
|
||||
User user,
|
||||
List<Reaction> latestReactions,
|
||||
List<Reaction> ownReactions,
|
||||
Message quotedMessage,
|
||||
}) {
|
||||
return Message(
|
||||
shadowed: shadowed,
|
||||
latestReactions: latestReactions,
|
||||
ownReactions: ownReactions,
|
||||
attachments: attachments?.map((it) {
|
||||
final json = jsonDecode(it);
|
||||
return Attachment.fromJson(json);
|
||||
})?.toList(),
|
||||
createdAt: createdAt,
|
||||
extraData: extraData,
|
||||
updatedAt: updatedAt,
|
||||
id: id,
|
||||
type: type,
|
||||
status: status,
|
||||
command: command,
|
||||
parentId: parentId,
|
||||
quotedMessageId: quotedMessageId,
|
||||
quotedMessage: quotedMessage,
|
||||
reactionCounts: reactionCounts,
|
||||
reactionScores: reactionScores,
|
||||
replyCount: replyCount,
|
||||
showInChannel: showInChannel,
|
||||
text: messageText,
|
||||
user: user,
|
||||
deletedAt: deletedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension MessageX on Message {
|
||||
///
|
||||
MessageEntity toEntity({String cid}) {
|
||||
return MessageEntity(
|
||||
id: id,
|
||||
attachments: attachments.map((it) => jsonEncode(it)).toList(),
|
||||
channelCid: cid,
|
||||
type: type,
|
||||
parentId: parentId,
|
||||
quotedMessageId: quotedMessageId,
|
||||
command: command,
|
||||
createdAt: createdAt,
|
||||
shadowed: shadowed,
|
||||
showInChannel: showInChannel,
|
||||
replyCount: replyCount,
|
||||
reactionScores: reactionScores,
|
||||
reactionCounts: reactionCounts,
|
||||
status: status,
|
||||
updatedAt: updatedAt,
|
||||
extraData: extraData,
|
||||
userId: user?.id,
|
||||
deletedAt: deletedAt,
|
||||
messageText: text,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
///
|
||||
extension ReactionEntityX on ReactionEntity {
|
||||
///
|
||||
Reaction toReaction({User user}) {
|
||||
return Reaction(
|
||||
extraData: extraData,
|
||||
type: type,
|
||||
createdAt: createdAt,
|
||||
userId: userId,
|
||||
user: user,
|
||||
messageId: messageId,
|
||||
score: score,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension ReactionX on Reaction {
|
||||
///
|
||||
ReactionEntity toEntity() {
|
||||
return ReactionEntity(
|
||||
extraData: extraData,
|
||||
type: type,
|
||||
createdAt: createdAt,
|
||||
userId: userId,
|
||||
messageId: messageId,
|
||||
score: score,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
import 'user_mapper.dart';
|
||||
|
||||
///
|
||||
extension ReadEntityX on ReadEntity {
|
||||
///
|
||||
Read toRead({User user}) {
|
||||
return Read(
|
||||
user: user,
|
||||
lastRead: lastRead,
|
||||
unreadMessages: unreadMessages,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension ReadX on Read {
|
||||
///
|
||||
ReadEntity toEntity({String cid}) {
|
||||
return ReadEntity(
|
||||
lastRead: lastRead,
|
||||
userId: user?.id,
|
||||
channelCid: cid,
|
||||
unreadMessages: unreadMessages,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_persistence/src/db/moor_chat_database.dart';
|
||||
|
||||
///
|
||||
extension UserEntityX on UserEntity {
|
||||
///
|
||||
User toUser() {
|
||||
return User(
|
||||
id: id,
|
||||
updatedAt: updatedAt,
|
||||
role: role,
|
||||
online: online,
|
||||
lastActive: lastActive,
|
||||
extraData: extraData,
|
||||
banned: banned,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
extension UserX on User {
|
||||
///
|
||||
UserEntity toEntity() {
|
||||
return UserEntity(
|
||||
id: id,
|
||||
role: role,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
online: online,
|
||||
banned: banned,
|
||||
extraData: extraData,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
library stream_chat_persistence;
|
||||
|
||||
export 'src/db/stream_chat_database_impl.dart';
|
||||
@@ -0,0 +1,18 @@
|
||||
name: stream_chat_persistence
|
||||
description: A new Flutter package.
|
||||
version: 0.0.1
|
||||
author:
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
moor: ^3.4.0
|
||||
stream_chat:
|
||||
path: ../dart_client
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.15.7
|
||||
build_runner: ^1.10.13
|
||||
moor_generator: ^3.4.1
|
||||
Reference in New Issue
Block a user