Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
5.9 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'dart:typed_data';
  4. import 'package:Envelope/database/models/conversation_users.dart';
  5. import 'package:sqflite/sql.dart';
  6. import 'package:uuid/uuid.dart';
  7. import '/database/models/conversations.dart';
  8. import '/database/models/friends.dart';
  9. import '/database/models/my_profile.dart';
  10. import '/utils/encryption/aes_helper.dart';
  11. import '/utils/strings.dart';
  12. import '/utils/storage/database.dart';
  13. class ConversationsRepository {
  14. static Future<Conversation> createConversation(String title, List<Friend> friends, bool twoUser) async {
  15. final db = await getDatabaseConnection();
  16. MyProfile profile = await MyProfile.getProfile();
  17. var uuid = const Uuid();
  18. final String conversationId = uuid.v4();
  19. Uint8List symmetricKey = AesHelper.deriveKey(generateRandomString(32));
  20. Conversation conversation = Conversation(
  21. id: conversationId,
  22. userId: profile.id,
  23. symmetricKey: base64.encode(symmetricKey),
  24. admin: true,
  25. name: title,
  26. twoUser: twoUser,
  27. status: ConversationStatus.pending,
  28. isRead: true,
  29. messageExpiryDefault: 'no_expiry',
  30. adminAddMembers: true,
  31. adminEditInfo: true,
  32. adminSendMessages: false,
  33. );
  34. await db.insert(
  35. 'conversations',
  36. conversation.toMap(),
  37. conflictAlgorithm: ConflictAlgorithm.replace,
  38. );
  39. await db.insert(
  40. 'conversation_users',
  41. ConversationUser(
  42. id: uuid.v4(),
  43. userId: profile.id,
  44. conversationId: conversationId,
  45. username: profile.username,
  46. associationKey: uuid.v4(),
  47. publicKey: profile.publicKey!,
  48. admin: true,
  49. ).toMap(),
  50. conflictAlgorithm: ConflictAlgorithm.fail,
  51. );
  52. for (Friend friend in friends) {
  53. await db.insert(
  54. 'conversation_users',
  55. ConversationUser(
  56. id: uuid.v4(),
  57. userId: friend.friendId,
  58. conversationId: conversationId,
  59. username: friend.username,
  60. associationKey: uuid.v4(),
  61. publicKey: friend.publicKey,
  62. admin: twoUser ? true : false,
  63. ).toMap(),
  64. conflictAlgorithm: ConflictAlgorithm.replace,
  65. );
  66. }
  67. if (twoUser) {
  68. List<Map<String, dynamic>> maps = await db.query(
  69. 'conversation_users',
  70. where: 'conversation_id = ? AND user_id != ?',
  71. whereArgs: [ conversation.id, profile.id ],
  72. );
  73. if (maps.length != 1) {
  74. throw ArgumentError('Invalid user id');
  75. }
  76. conversation.name = maps[0]['username'];
  77. await db.insert(
  78. 'conversations',
  79. conversation.toMap(),
  80. conflictAlgorithm: ConflictAlgorithm.replace,
  81. );
  82. }
  83. return conversation;
  84. }
  85. static Future<Conversation> getConversationById(String id) async {
  86. final db = await getDatabaseConnection();
  87. final List<Map<String, dynamic>> maps = await db.query(
  88. 'conversations',
  89. where: 'id = ?',
  90. whereArgs: [id],
  91. );
  92. if (maps.length != 1) {
  93. throw ArgumentError('Invalid user id');
  94. }
  95. File? file;
  96. if (maps[0]['file'] != null && maps[0]['file'] != '') {
  97. file = File(maps[0]['file']);
  98. }
  99. return Conversation(
  100. id: maps[0]['id'],
  101. userId: maps[0]['user_id'],
  102. symmetricKey: maps[0]['symmetric_key'],
  103. admin: maps[0]['admin'] == 1,
  104. name: maps[0]['name'],
  105. twoUser: maps[0]['two_user'] == 1,
  106. status: ConversationStatus.values[maps[0]['status']],
  107. isRead: maps[0]['is_read'] == 1,
  108. icon: file,
  109. messageExpiryDefault: maps[0]['message_expiry'],
  110. adminAddMembers: maps[0]['admin_add_members'] == 1,
  111. adminEditInfo: maps[0]['admin_edit_info'] == 1,
  112. adminSendMessages: maps[0]['admin_send_messages'] == 1,
  113. );
  114. }
  115. static Future<List<Conversation>> getConversations() async {
  116. final db = await getDatabaseConnection();
  117. final List<Map<String, dynamic>> maps = await db.query(
  118. 'conversations',
  119. orderBy: 'name',
  120. );
  121. return List.generate(maps.length, (i) {
  122. File? file;
  123. if (maps[i]['file'] != null && maps[i]['file'] != '') {
  124. file = File(maps[i]['file']);
  125. }
  126. return Conversation(
  127. id: maps[i]['id'],
  128. userId: maps[i]['user_id'],
  129. symmetricKey: maps[i]['symmetric_key'],
  130. admin: maps[i]['admin'] == 1,
  131. name: maps[i]['name'],
  132. twoUser: maps[i]['two_user'] == 1,
  133. status: ConversationStatus.values[maps[i]['status']],
  134. isRead: maps[i]['is_read'] == 1,
  135. icon: file,
  136. messageExpiryDefault: maps[i]['message_expiry'] ?? 'no_expiry',
  137. adminAddMembers: maps[i]['admin_add_members'] == 1,
  138. adminEditInfo: maps[i]['admin_edit_info'] == 1,
  139. adminSendMessages: maps[i]['admin_send_messages'] == 1,
  140. );
  141. });
  142. }
  143. static Future<Conversation?> getTwoUserConversation(String userId) async {
  144. final db = await getDatabaseConnection();
  145. MyProfile profile = await MyProfile.getProfile();
  146. final List<Map<String, dynamic>> maps = await db.rawQuery(
  147. '''
  148. SELECT conversations.* FROM conversations
  149. LEFT JOIN conversation_users ON conversation_users.conversation_id = conversations.id
  150. WHERE conversation_users.user_id = ?
  151. AND conversation_users.user_id != ?
  152. AND conversations.two_user = 1
  153. ''',
  154. [ userId, profile.id ],
  155. );
  156. if (maps.length != 1) {
  157. return null;
  158. }
  159. return Conversation(
  160. id: maps[0]['id'],
  161. userId: maps[0]['user_id'],
  162. symmetricKey: maps[0]['symmetric_key'],
  163. admin: maps[0]['admin'] == 1,
  164. name: maps[0]['name'],
  165. twoUser: maps[0]['two_user'] == 1,
  166. status: ConversationStatus.values[maps[0]['status']],
  167. isRead: maps[0]['is_read'] == 1,
  168. messageExpiryDefault: maps[0]['message_expiry'],
  169. adminAddMembers: maps[0]['admin_add_members'] == 1,
  170. adminEditInfo: maps[0]['admin_edit_info'] == 1,
  171. adminSendMessages: maps[0]['admin_send_messages'] == 1,
  172. );
  173. }
  174. }