Browse Source

chat duplicates fix

Viktoriia 1 tháng trước cách đây
mục cha
commit
1c2265f352

+ 5 - 1
Route.tsx

@@ -116,6 +116,8 @@ import Trips2025Screen from 'src/screens/InAppScreens/TravelsScreen/Trips2025Scr
 import AddNewTrip2025Screen from 'src/screens/InAppScreens/TravelsScreen/AddNewTrip2025Screen';
 import AddRegionsNewScreen from 'src/screens/InAppScreens/TravelsScreen/AddRegionsNewScreen';
 import RegionsVisitedScreen from 'src/screens/InAppScreens/TravelsScreen/RegionsVisitedScreen';
+import { clearLocalDatabaseOnLogout } from 'src/watermelondb/features/chat/data/chat.repo';
+import { stopAutoSyncListener } from 'src/watermelondb/features/chat/networkSync';
 
 enableScreens();
 
@@ -181,8 +183,10 @@ const Route = () => {
     return children;
   }
 
-  const handleLogout = () => {
+  const handleLogout = async () => {
     setToken(null);
+    stopAutoSyncListener();
+    await clearLocalDatabaseOnLogout();
     storage.remove('token');
     storage.remove('uid');
     storage.remove('currentUserData');

+ 6 - 1
src/components/ErrorModal/index.tsx

@@ -14,6 +14,8 @@ import { NAVIGATION_PAGES } from 'src/types';
 import { storage } from 'src/storage';
 import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
+import { clearLocalDatabaseOnLogout } from 'src/watermelondb/features/chat/data/chat.repo';
+import { stopAutoSyncListener } from 'src/watermelondb/features/chat/networkSync';
 
 export const ErrorModal = () => {
   const { error, hideError, navigateToLogin, navigateToAuth, premiumError, resetErrorState } =
@@ -24,8 +26,11 @@ export const ErrorModal = () => {
   );
   const updateUnreadMessagesCount = useMessagesStore((state) => state.updateUnreadMessagesCount);
 
-  const handleClose = () => {
+  const handleClose = async () => {
     if (navigateToLogin) {
+      stopAutoSyncListener();
+      await clearLocalDatabaseOnLogout();
+
       storage.remove('token');
       storage.remove('uid');
       storage.remove('currentUserData');

+ 2 - 0
src/database/index.ts

@@ -12,6 +12,7 @@ import { cleanCache, deleteAvatarsDirectory } from './cacheService';
 import { database } from 'src/watermelondb';
 import { hasLocalBackup } from 'src/watermelondb/backup';
 import { importChatsFromServer } from 'src/watermelondb/features/chat/data/importChatsFromServer';
+import { dedupeChats } from 'src/watermelondb/features/chat/data/chat.repo';
 
 const lastUpdateNmRegions =
   (storage.get('lastUpdateNmRegions', StoreType.STRING) as string) || '1990-01-01';
@@ -73,6 +74,7 @@ export const updateMasterRanking = async () => {
 export async function initializeDatabase(token: string) {
   const chats = await database.get('chats').query().fetch();
   if (chats.length) {
+    await dedupeChats();
     console.log('🟢 Database already initialized');
     return;
   } else {

+ 6 - 1
src/screens/InAppScreens/ProfileScreen/Profile/edit-personal-info.tsx

@@ -48,6 +48,8 @@ import { useMessagesStore } from 'src/stores/unreadMessagesStore';
 import { useAvatarStore } from 'src/stores/avatarVersionStore';
 import { useFriendsNotificationsStore } from 'src/stores/friendsNotificationsStore';
 import { getFontSize } from 'src/utils';
+import { clearLocalDatabaseOnLogout } from 'src/watermelondb/features/chat/data/chat.repo';
+import { stopAutoSyncListener } from 'src/watermelondb/features/chat/networkSync';
 
 const ProfileSchema = yup.object({
   username: yup.string().optional(),
@@ -113,7 +115,10 @@ export const EditPersonalInfo = () => {
   const originRegion = regions.data?.data.find((region) => region.id === data.homebase);
   const secondOrigin = regions.data?.data.find((region) => region.id === data.homebase2);
 
-  const handleLogout = () => {
+  const handleLogout = async () => {
+    stopAutoSyncListener();
+    await clearLocalDatabaseOnLogout();
+
     storage.remove('token');
     storage.remove('uid');
     storage.remove('currentUserData');

+ 4 - 4
src/watermelondb/features/chat/data/blocked.repo.ts

@@ -1,3 +1,4 @@
+import { Q } from '@nozbe/watermelondb';
 import { database } from 'src/watermelondb';
 import BlockedUser from 'src/watermelondb/models/BlockedUser';
 
@@ -11,17 +12,16 @@ type Blocked = {
 export async function upsertBlockedUsers(users: Blocked[]) {
   if (!users?.length) return;
   const collection = database.get<BlockedUser>('blocked_users');
-  const existingBlocked = await collection.query().fetch();
 
   await database.write(async () => {
     const batch: any[] = [];
 
     for (const u of users) {
-      const existing = existingBlocked.find((ec) => ec.userId && ec.userId === u.id);
+      const existingNow = await collection.query(Q.where('user_id', u.id)).fetch();
 
-      if (existing) {
+      if (existingNow.length) {
         batch.push(
-          existing.prepareUpdate((rec) => {
+          existingNow[0].prepareUpdate((rec) => {
             rec.firstName = u.first_name;
             rec.lastName = u.last_name;
             rec.avatar = u.avatar ?? null;

+ 61 - 8
src/watermelondb/features/chat/data/chat.repo.ts

@@ -1,3 +1,4 @@
+import { Q } from '@nozbe/watermelondb';
 import { database } from 'src/watermelondb';
 import Chat from 'src/watermelondb/models/Chat';
 
@@ -29,21 +30,29 @@ export async function upsertChats(chats: ServerChat[]) {
   if (!chats?.length) return;
 
   const chatCollection = database.get<Chat>('chats');
-  const existingChats = await chatCollection.query().fetch();
+  const seen = new Set<string>();
+
+  const makeKey = (c: ServerChat) => (c.uid ? `u:${c.uid}` : `g:${c.group_chat_token}`);
 
   await database.write(async () => {
     const batch: any[] = [];
 
     for (const c of chats) {
-      const existing = existingChats.find(
-        (ec) =>
-          (ec.chatUid && ec.chatUid === c.uid) ||
-          (ec.groupChatToken && ec.groupChatToken === c.group_chat_token)
-      );
+      const key = makeKey(c);
+
+      if (seen.has(key)) {
+        continue;
+      }
 
-      if (existing) {
+      seen.add(key);
+
+      const existingNow = await chatCollection
+        .query(c.uid ? Q.where('chat_uid', c.uid) : Q.where('group_chat_token', c.group_chat_token))
+        .fetch();
+
+      if (existingNow.length) {
         batch.push(
-          existing.prepareUpdate((rec) => {
+          existingNow[0].prepareUpdate((rec) => {
             rec.name = c.name;
             rec.avatar = c.avatar ?? null;
             rec.short = c.short;
@@ -102,3 +111,47 @@ export async function upsertChats(chats: ServerChat[]) {
     console.log('upsertChats complete');
   });
 }
+
+export async function dedupeChats() {
+  const chatCollection = database.get<Chat>('chats');
+  const chats = await chatCollection.query().fetch();
+
+  const map = new Map<string, Chat[]>();
+
+  for (const c of chats) {
+    const key = c.chatUid
+      ? `dm:${c.chatUid}`
+      : c.groupChatToken
+        ? `group:${c.groupChatToken}`
+        : null;
+
+    if (!key) continue;
+
+    if (!map.has(key)) map.set(key, []);
+    map.get(key)!.push(c);
+  }
+
+  await database.write(async () => {
+    for (const [, list] of map) {
+      if (list.length <= 1) continue;
+
+      list.sort((a, b) => {
+        if (a.isDirty && !b.isDirty) return -1;
+        if (!a.isDirty && b.isDirty) return 1;
+        return b.updated - a.updated;
+      });
+
+      const [, ...duplicates] = list;
+
+      for (const d of duplicates) {
+        await d.destroyPermanently();
+      }
+    }
+  });
+}
+
+export async function clearLocalDatabaseOnLogout() {
+  await database.write(async () => {
+    await database.unsafeResetDatabase();
+  });
+}

+ 29 - 4
src/watermelondb/features/chat/data/chat.sync.ts

@@ -295,11 +295,35 @@ async function pullUpdates(token: string) {
   const localKeyFor = (c: Chat) => (c.groupChatToken ? `g:${c.groupChatToken}` : `u:${c.chatUid}`);
 
   const localMap = new Map<string, Chat>();
+  const localDuplicates = new Map<string, Chat[]>();
   for (const c of localChats) {
     const k = localKeyFor(c);
-    localMap.set(k, c);
+
+    if (!localMap.has(k)) {
+      localMap.set(k, c);
+    } else {
+      if (!localDuplicates.has(k)) {
+        localDuplicates.set(k, [localMap.get(k)!]);
+      }
+      localDuplicates.get(k)!.push(c);
+    }
   }
 
+  await database.write(async () => {
+    for (const [, list] of localDuplicates) {
+      list.sort((a, b) => {
+        if (a.isDirty && !b.isDirty) return -1;
+        if (!a.isDirty && b.isDirty) return 1;
+        return b.updated - a.updated;
+      });
+
+      const [, ...duplicates] = list;
+      for (const d of duplicates) {
+        await d.destroyPermanently();
+      }
+    }
+  });
+
   const serverMap = new Map<string, any>();
   for (const s of serverChats) {
     const k = keyForServer(s);
@@ -338,6 +362,10 @@ async function pullUpdates(token: string) {
 
     for (const item of toCreate) {
       const s = item.serverChat;
+      if (localMap.has(item.key)) {
+        continue;
+      }
+
       batch.push(
         chatCollection.prepareCreate((r: any) => {
           r.chatUid = s.uid ?? s.chat_uid ?? null;
@@ -345,9 +373,6 @@ async function pullUpdates(token: string) {
 
           r.name = s.name ?? '';
           r.avatar = s.avatar ?? null;
-          r.avatarLocal = s.avatar_local ?? null;
-          r.avatarEtag = s.avatar_etag ?? null;
-          r.avatarCheckedAt = s.avatar_checked_at ?? null;
 
           r.short = s.short ?? '';
           r.sentBy = s.sent_by ?? 0;