From 87394eb0a11c365a62953cb2fc9cf0d4881380e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Wed, 10 Dec 2025 14:46:05 +0200 Subject: [PATCH 01/29] Minor refactor - fix comment - add `getUserType` helper, use it --- libs/wire-api/src/Wire/API/User/Search.hs | 2 +- .../src/Wire/UserSubsystem/Interpreter.hs | 49 ++++++++++++------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/libs/wire-api/src/Wire/API/User/Search.hs b/libs/wire-api/src/Wire/API/User/Search.hs index bddf084994..b071d3695b 100644 --- a/libs/wire-api/src/Wire/API/User/Search.hs +++ b/libs/wire-api/src/Wire/API/User/Search.hs @@ -138,7 +138,7 @@ deriving via (Schema (SearchResult TeamContact)) instance S.ToSchema (SearchResu -------------------------------------------------------------------------------- -- Contact --- | Returned by 'searchIndex' under @/contacts/search@. +-- | Returned by 'searchIndex' under @/search/contacts@. -- This is a subset of 'User' and json instances should reflect that. data Contact = Contact { contactQualifiedId :: Qualified UserId, diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 86be662b3c..940ba76352 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -453,11 +453,9 @@ getLocalUserProfileImpl emailVisibilityConfigWithViewer luid = do pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) teamMember let user = mkUserFromStored domain locale storedUser usrProfile = mkUserProfile emailVisibilityConfigWithViewer user lhs - app <- lift $ mapM (getApp storedUser.id) storedUser.teamId + userType <- lift $ getUserType storedUser.id storedUser.teamId storedUser.serviceId lift $ deleteLocalIfExpired user - pure $ case join app of - Nothing -> usrProfile - Just _ -> usrProfile {profileType = UserTypeApp} + pure $ usrProfile {profileType = userType} getSelfProfileImpl :: ( Member (Input UserSubsystemConfig) r, @@ -864,27 +862,22 @@ searchLocally searcher searchTerm maybeMaxResults = do exactHandleSearch :: Sem r (Maybe Contact) exactHandleSearch = runMaybeT $ do - handle <- MaybeT . pure $ Handle.parseHandle searchTerm + handle <- hoistMaybe $ Handle.parseHandle searchTerm owner <- MaybeT $ UserStore.lookupHandle handle storedUser <- MaybeT $ UserStore.getUser owner config <- lift input - let contact = contactFromStoredUser (tDomain searcher) storedUser - isContactVisible = + let isContactVisible = (config.searchSameTeamOnly && (snd . tUnqualified $ searcher) == storedUser.teamId) || (not config.searchSameTeamOnly) if isContactVisible && fromMaybe True storedUser.searchable - then pure contact - else MaybeT $ pure Nothing - - contactFromStoredUser :: Domain -> StoredUser -> Contact - contactFromStoredUser domain storedUser = - Contact - { contactQualifiedId = Qualified storedUser.id domain, - contactName = fromName storedUser.name, - contactHandle = Handle.fromHandle <$> storedUser.handle, - contactColorId = Just . fromIntegral . fromColourId $ storedUser.accentId, - contactTeam = storedUser.teamId - } + then pure $ Contact + { contactQualifiedId = Qualified storedUser.id (tDomain searcher), + contactName = fromName storedUser.name, + contactHandle = Handle.fromHandle <$> storedUser.handle, + contactColorId = Just . fromIntegral . fromColourId $ storedUser.accentId, + contactTeam = storedUser.teamId + } + else hoistMaybe Nothing searchRemotely :: ( Member FederationConfigStore r, @@ -1166,3 +1159,21 @@ setUserSearchableImpl luid uid searchable = do ensurePermissions (tUnqualified luid) tid [SetMemberSearchable] UserStore.setUserSearchable uid searchable syncUserIndex uid + +-- * Helpers + +getUserType :: + forall r. + ( Member AppStore r + ) => + UserId -> + Maybe TeamId -> + Maybe ServiceId -> + Sem r UserType +getUserType uid mTid mbServiceId = case mbServiceId of + Just _ -> pure UserTypeBot + Nothing -> do + mmApp <- mapM (getApp uid) mTid + case join mmApp of + Just _ -> pure UserTypeApp + Nothing -> pure UserTypeRegular From 2a220a5acbe2d69445c8e74a10b87377cba97adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Fri, 26 Dec 2025 18:33:30 +0200 Subject: [PATCH 02/29] Add integration test --- integration/test/Test/Apps.hs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/integration/test/Test/Apps.hs b/integration/test/Test/Apps.hs index 4af7b903b3..95bdf6e4cc 100644 --- a/integration/test/Test/Apps.hs +++ b/integration/test/Test/Apps.hs @@ -88,13 +88,23 @@ testCreateApp = do void $ bindResponse (createApp owner tid new {category = "notinenum"}) $ \resp -> do resp.status `shouldMatchInt` 400 + let foundUserType exactMatchTerm aType = + searchContacts owner exactMatchTerm OwnDomain `bindResponse` \resp -> do + resp.status `shouldMatchInt` 200 + foundDoc <- resp.json %. "documents" >>= asList >>= assertOne + foundDoc %. "type" `shouldMatch` aType + -- App's user is findable from /search/contacts BrigI.refreshIndex OwnDomain - searchContacts owner new.name OwnDomain `bindResponse` \resp -> do - resp.status `shouldMatchInt` 200 - docs <- resp.json %. "documents" >>= asList - foundUids <- for docs objId - foundUids `shouldMatch` [appId] + foundUserType new.name "app" + + -- Owner and regular member still have the type "regular" + memberName <- regularMember %. "name" & asString + foundUserType memberName "regular" + +-- XXX: Why is owner not found? +-- ownerName <- owner %. "name" & asString +-- foundUserType ownerName "regular" testRefreshAppCookie :: (HasCallStack) => App () testRefreshAppCookie = do From 061804c8be8c6ae6d9a52498ecbd83033d8a5370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Tue, 30 Dec 2025 16:34:17 +0200 Subject: [PATCH 03/29] Add field to golden tests --- .../Wire/API/Golden/Generated/Contact_user.hs | 61 +++++++---- .../Generated/SearchResult_20Contact_user.hs | 103 ++++++++++++------ .../Test/Wire/API/Golden/Manual/Contact.hs | 7 +- .../test/golden/testObject_Contact_1.json | 3 +- .../test/golden/testObject_Contact_2.json | 3 +- .../golden/testObject_Contact_user_1.json | 3 +- .../golden/testObject_Contact_user_10.json | 3 +- .../golden/testObject_Contact_user_11.json | 3 +- .../golden/testObject_Contact_user_12.json | 3 +- .../golden/testObject_Contact_user_13.json | 3 +- .../golden/testObject_Contact_user_14.json | 3 +- .../golden/testObject_Contact_user_15.json | 3 +- .../golden/testObject_Contact_user_16.json | 3 +- .../golden/testObject_Contact_user_17.json | 3 +- .../golden/testObject_Contact_user_18.json | 3 +- .../golden/testObject_Contact_user_19.json | 3 +- .../golden/testObject_Contact_user_2.json | 3 +- .../golden/testObject_Contact_user_20.json | 3 +- .../golden/testObject_Contact_user_3.json | 3 +- .../golden/testObject_Contact_user_4.json | 3 +- .../golden/testObject_Contact_user_5.json | 3 +- .../golden/testObject_Contact_user_6.json | 3 +- .../golden/testObject_Contact_user_7.json | 3 +- .../golden/testObject_Contact_user_8.json | 3 +- .../golden/testObject_Contact_user_9.json | 3 +- .../testObject_SearchResultContact_1.json | 6 +- .../testObject_SearchResultContact_2.json | 6 +- ...Object_SearchResult_20Contact_user_11.json | 6 +- ...Object_SearchResult_20Contact_user_13.json | 12 +- ...Object_SearchResult_20Contact_user_14.json | 6 +- ...Object_SearchResult_20Contact_user_19.json | 6 +- ...Object_SearchResult_20Contact_user_20.json | 39 ++++--- ...tObject_SearchResult_20Contact_user_3.json | 3 +- ...tObject_SearchResult_20Contact_user_4.json | 18 ++- ...tObject_SearchResult_20Contact_user_5.json | 3 +- ...tObject_SearchResult_20Contact_user_7.json | 6 +- ...tObject_SearchResult_20Contact_user_8.json | 3 +- 37 files changed, 235 insertions(+), 116 deletions(-) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs index b3f7c67e29..86050a826a 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs @@ -22,6 +22,7 @@ import Data.Id (Id (Id)) import Data.Qualified (Qualified (Qualified, qDomain, qUnqualified)) import Data.UUID qualified as UUID (fromString) import Imports (Maybe (Just, Nothing), fromJust) +import Wire.API.User (UserType (UserTypeRegular)) import Wire.API.User.Search (Contact (..)) testObject_Contact_user_1 :: Contact @@ -35,7 +36,8 @@ testObject_Contact_user_1 = contactName = "", contactColorId = Just 6, contactHandle = Just "\1089530\NUL|\SO", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } testObject_Contact_user_2 :: Contact @@ -49,7 +51,8 @@ testObject_Contact_user_2 = contactName = "\SYND", contactColorId = Just (-5), contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000002-0000-0008-0000-000400000002"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000002-0000-0008-0000-000400000002"))), + contactType = UserTypeRegular } testObject_Contact_user_3 :: Contact @@ -63,7 +66,8 @@ testObject_Contact_user_3 = contactName = "S\1037187D\GS", contactColorId = Just (-4), contactHandle = Just "\175177~\35955c", - contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0005-0000-000700000008"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0005-0000-000700000008"))), + contactType = UserTypeRegular } testObject_Contact_user_4 :: Contact @@ -77,7 +81,8 @@ testObject_Contact_user_4 = contactName = "@=\ETX", contactColorId = Nothing, contactHandle = Just "6", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000500000004"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000500000004"))), + contactType = UserTypeRegular } testObject_Contact_user_5 :: Contact @@ -91,7 +96,8 @@ testObject_Contact_user_5 = contactName = "5m~\DC4`", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } testObject_Contact_user_6 :: Contact @@ -105,7 +111,8 @@ testObject_Contact_user_6 = contactName = "Cst\995547U", contactColorId = Nothing, contactHandle = Just "qI", - contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0004-0000-000600000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0004-0000-000600000000"))), + contactType = UserTypeRegular } testObject_Contact_user_7 :: Contact @@ -119,7 +126,8 @@ testObject_Contact_user_7 = contactName = "\b74\ENQ", contactColorId = Just 5, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000008-0000-0001-0000-000400000008"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000008-0000-0001-0000-000400000008"))), + contactType = UserTypeRegular } testObject_Contact_user_8 :: Contact @@ -133,7 +141,8 @@ testObject_Contact_user_8 = contactName = "w\1050194\993461#\\", contactColorId = Just (-2), contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0007-0000-000500000002"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0007-0000-000500000002"))), + contactType = UserTypeRegular } testObject_Contact_user_9 :: Contact @@ -147,7 +156,8 @@ testObject_Contact_user_9 = contactName = ",\1041199 \v\1077257", contactColorId = Just 5, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0002-0000-000500000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0002-0000-000500000000"))), + contactType = UserTypeRegular } testObject_Contact_user_10 :: Contact @@ -161,7 +171,8 @@ testObject_Contact_user_10 = contactName = "(\1103086\1105553H/", contactColorId = Just 0, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0006-0000-000700000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0006-0000-000700000000"))), + contactType = UserTypeRegular } testObject_Contact_user_11 :: Contact @@ -175,7 +186,8 @@ testObject_Contact_user_11 = contactName = "+\DC4\1063683<", contactColorId = Just 6, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000007-0000-0008-0000-000600000004"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000007-0000-0008-0000-000600000004"))), + contactType = UserTypeRegular } testObject_Contact_user_12 :: Contact @@ -189,7 +201,8 @@ testObject_Contact_user_12 = contactName = "l\DC1\ETB`\ETX", contactColorId = Just (-4), contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } testObject_Contact_user_13 :: Contact @@ -203,7 +216,8 @@ testObject_Contact_user_13 = contactName = "\SYN\1030541\v8z", contactColorId = Just (-3), contactHandle = Just "E\EM\US[58", - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0003-0000-000000000005"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0003-0000-000000000005"))), + contactType = UserTypeRegular } testObject_Contact_user_14 :: Contact @@ -217,7 +231,8 @@ testObject_Contact_user_14 = contactName = "7", contactColorId = Just (-2), contactHandle = Just "h\CAN", - contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0008-0000-000700000008"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000005-0000-0008-0000-000700000008"))), + contactType = UserTypeRegular } testObject_Contact_user_15 :: Contact @@ -231,7 +246,8 @@ testObject_Contact_user_15 = contactName = "U6\ESC*\SO", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0006-0000-000800000006"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0006-0000-000800000006"))), + contactType = UserTypeRegular } testObject_Contact_user_16 :: Contact @@ -245,7 +261,8 @@ testObject_Contact_user_16 = contactName = "l", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0006-0000-000200000007"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0006-0000-000200000007"))), + contactType = UserTypeRegular } testObject_Contact_user_17 :: Contact @@ -259,7 +276,8 @@ testObject_Contact_user_17 = contactName = "fI\8868\&3z", contactColorId = Nothing, contactHandle = Just "3", - contactTeam = Just (Id (fromJust (UUID.fromString "00000004-0000-0007-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000004-0000-0007-0000-000000000001"))), + contactType = UserTypeRegular } testObject_Contact_user_18 :: Contact @@ -273,7 +291,8 @@ testObject_Contact_user_18 = contactName = "\"jC\74801\144577\DC2", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000007"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000007"))), + contactType = UserTypeRegular } testObject_Contact_user_19 :: Contact @@ -287,7 +306,8 @@ testObject_Contact_user_19 = contactName = "I", contactColorId = Just (-1), contactHandle = Just "\"7\ACK!", - contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0004-0000-000000000003"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0004-0000-000000000003"))), + contactType = UserTypeRegular } testObject_Contact_user_20 :: Contact @@ -301,5 +321,6 @@ testObject_Contact_user_20 = contactName = "|K\n\n\t", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SearchResult_20Contact_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SearchResult_20Contact_user.hs index bb79681738..1b9dc63b00 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SearchResult_20Contact_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/SearchResult_20Contact_user.hs @@ -24,6 +24,7 @@ import Data.Id (Id (Id)) import Data.Qualified (Qualified (Qualified, qDomain, qUnqualified)) import Data.UUID qualified as UUID (fromString) import Imports (Bool (..), Maybe (Just, Nothing), fromJust) +import Wire.API.User (UserType (UserTypeRegular)) import Wire.API.User.Search (Contact (..), FederatedUserSearchPolicy (ExactHandleSearch, FullSearch), PagingState (..), SearchResult (..)) testObject_SearchResult_20Contact_user_1 :: SearchResult Contact @@ -66,7 +67,8 @@ testObject_SearchResult_20Contact_user_3 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))), + contactType = UserTypeRegular } ], searchPolicy = FullSearch, @@ -90,7 +92,8 @@ testObject_SearchResult_20Contact_user_4 = contactName = "", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -101,7 +104,8 @@ testObject_SearchResult_20Contact_user_4 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -112,7 +116,8 @@ testObject_SearchResult_20Contact_user_4 = contactName = "", contactColorId = Just 0, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -123,7 +128,8 @@ testObject_SearchResult_20Contact_user_4 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -134,7 +140,8 @@ testObject_SearchResult_20Contact_user_4 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -145,7 +152,8 @@ testObject_SearchResult_20Contact_user_4 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000"))), + contactType = UserTypeRegular } ], searchPolicy = FullSearch, @@ -169,7 +177,8 @@ testObject_SearchResult_20Contact_user_5 = contactName = "z", contactColorId = Just 1, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))), + contactType = UserTypeRegular } ], searchPolicy = FullSearch, @@ -205,7 +214,8 @@ testObject_SearchResult_20Contact_user_7 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -216,7 +226,8 @@ testObject_SearchResult_20Contact_user_7 = contactName = "", contactColorId = Just 0, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))), + contactType = UserTypeRegular } ], searchPolicy = FullSearch, @@ -240,7 +251,8 @@ testObject_SearchResult_20Contact_user_8 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))), + contactType = UserTypeRegular } ], searchPolicy = FullSearch, @@ -288,7 +300,8 @@ testObject_SearchResult_20Contact_user_11 = contactName = "", contactColorId = Just 0, contactHandle = Nothing, - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -299,7 +312,8 @@ testObject_SearchResult_20Contact_user_11 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } ], searchPolicy = ExactHandleSearch, @@ -335,7 +349,8 @@ testObject_SearchResult_20Contact_user_13 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -346,7 +361,8 @@ testObject_SearchResult_20Contact_user_13 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -357,7 +373,8 @@ testObject_SearchResult_20Contact_user_13 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -368,7 +385,8 @@ testObject_SearchResult_20Contact_user_13 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } ], searchPolicy = ExactHandleSearch, @@ -392,7 +410,8 @@ testObject_SearchResult_20Contact_user_14 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -403,7 +422,8 @@ testObject_SearchResult_20Contact_user_14 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } ], searchPolicy = ExactHandleSearch, @@ -475,7 +495,8 @@ testObject_SearchResult_20Contact_user_19 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -486,7 +507,8 @@ testObject_SearchResult_20Contact_user_19 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))), + contactType = UserTypeRegular } ], searchPolicy = ExactHandleSearch, @@ -510,7 +532,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -521,7 +544,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -532,7 +556,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -543,7 +568,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -554,7 +580,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -565,7 +592,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -576,7 +604,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -587,7 +616,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -598,7 +628,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Nothing, contactHandle = Just "", - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -609,7 +640,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -620,7 +652,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -631,7 +664,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Just "", - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))), + contactType = UserTypeRegular }, Contact { contactQualifiedId = @@ -642,7 +676,8 @@ testObject_SearchResult_20Contact_user_20 = contactName = "", contactColorId = Just 0, contactHandle = Nothing, - contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))) + contactTeam = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))), + contactType = UserTypeRegular } ], searchPolicy = ExactHandleSearch, diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Contact.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Contact.hs index 513f6d30af..3b2bc6fed9 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Contact.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Manual/Contact.hs @@ -22,6 +22,7 @@ import Data.Id (Id (Id)) import Data.Qualified (Qualified (Qualified)) import Data.UUID qualified as UUID import Imports +import Wire.API.User (UserType (UserTypeRegular)) import Wire.API.User.Search (Contact (..)) testObject_Contact_1 :: Contact @@ -31,7 +32,8 @@ testObject_Contact_1 = contactName = "Foobar", contactColorId = Just 1, contactHandle = Just "foobar1", - contactTeam = Just $ Id (fromJust (UUID.fromString "00000018-0000-0020-0000-000e00000002")) + contactTeam = Just $ Id (fromJust (UUID.fromString "00000018-0000-0020-0000-000e00000002")), + contactType = UserTypeRegular } testObject_Contact_2 :: Contact @@ -41,5 +43,6 @@ testObject_Contact_2 = contactName = "Foobar2", contactColorId = Nothing, contactHandle = Nothing, - contactTeam = Nothing + contactTeam = Nothing, + contactType = UserTypeRegular } diff --git a/libs/wire-api/test/golden/testObject_Contact_1.json b/libs/wire-api/test/golden/testObject_Contact_1.json index fb1bdac6dd..01906d05a2 100644 --- a/libs/wire-api/test/golden/testObject_Contact_1.json +++ b/libs/wire-api/test/golden/testObject_Contact_1.json @@ -7,5 +7,6 @@ "domain": "example.com", "id": "00000018-0000-0020-0000-000e00000002" }, - "team": "00000018-0000-0020-0000-000e00000002" + "team": "00000018-0000-0020-0000-000e00000002", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_2.json b/libs/wire-api/test/golden/testObject_Contact_2.json index aca2362249..a7171d11e6 100644 --- a/libs/wire-api/test/golden/testObject_Contact_2.json +++ b/libs/wire-api/test/golden/testObject_Contact_2.json @@ -7,5 +7,6 @@ "domain": "another.example.com", "id": "00000018-0000-0020-0000-000e00000003" }, - "team": null + "team": null, + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_1.json b/libs/wire-api/test/golden/testObject_Contact_user_1.json index 825287dc78..44a3c28869 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_1.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_1.json @@ -7,5 +7,6 @@ "domain": "j00.8y.yr3isy2m", "id": "00000007-0000-0003-0000-000300000005" }, - "team": null + "team": null, + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_10.json b/libs/wire-api/test/golden/testObject_Contact_user_10.json index 7618387345..57c4e71fa2 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_10.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_10.json @@ -7,5 +7,6 @@ "domain": "avs-82k0.quv1k-5", "id": "00000000-0000-0000-0000-000800000007" }, - "team": "00000005-0000-0006-0000-000700000000" + "team": "00000005-0000-0006-0000-000700000000", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_11.json b/libs/wire-api/test/golden/testObject_Contact_user_11.json index f6831f331b..dfd13cc9a2 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_11.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_11.json @@ -7,5 +7,6 @@ "domain": "156y.t.qxp-y26x", "id": "00000002-0000-0005-0000-000700000004" }, - "team": "00000007-0000-0008-0000-000600000004" + "team": "00000007-0000-0008-0000-000600000004", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_12.json b/libs/wire-api/test/golden/testObject_Contact_user_12.json index 38ca139c60..0c504b2229 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_12.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_12.json @@ -7,5 +7,6 @@ "domain": "d2wnzbn.8.k2d4-103", "id": "00000004-0000-0002-0000-000300000003" }, - "team": null + "team": null, + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_13.json b/libs/wire-api/test/golden/testObject_Contact_user_13.json index 3ee4544fc4..81835b8a16 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_13.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_13.json @@ -7,5 +7,6 @@ "domain": "902cigj.v2t56", "id": "00000002-0000-0006-0000-000800000006" }, - "team": "00000001-0000-0003-0000-000000000005" + "team": "00000001-0000-0003-0000-000000000005", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_14.json b/libs/wire-api/test/golden/testObject_Contact_user_14.json index 876b00ee25..14ef9ebd56 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_14.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_14.json @@ -7,5 +7,6 @@ "domain": "6z.ml.80ps6j5r.l", "id": "00000000-0000-0003-0000-000300000006" }, - "team": "00000005-0000-0008-0000-000700000008" + "team": "00000005-0000-0008-0000-000700000008", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_15.json b/libs/wire-api/test/golden/testObject_Contact_user_15.json index 5664eab786..619c62f22a 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_15.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_15.json @@ -7,5 +7,6 @@ "domain": "739.e-h8g", "id": "00000002-0000-0000-0000-000200000002" }, - "team": "00000006-0000-0006-0000-000800000006" + "team": "00000006-0000-0006-0000-000800000006", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_16.json b/libs/wire-api/test/golden/testObject_Contact_user_16.json index a04c2bf386..1ba59c042a 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_16.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_16.json @@ -7,5 +7,6 @@ "domain": "t82.x5i8-i", "id": "00000000-0000-0006-0000-000500000006" }, - "team": "00000000-0000-0006-0000-000200000007" + "team": "00000000-0000-0006-0000-000200000007", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_17.json b/libs/wire-api/test/golden/testObject_Contact_user_17.json index fd68c31c3a..54bb4c0fab 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_17.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_17.json @@ -7,5 +7,6 @@ "domain": "o5b0hrjp3x0b96.v1gxp3", "id": "00000003-0000-0008-0000-000700000002" }, - "team": "00000004-0000-0007-0000-000000000001" + "team": "00000004-0000-0007-0000-000000000001", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_18.json b/libs/wire-api/test/golden/testObject_Contact_user_18.json index b5f25e02fc..5c2357f91b 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_18.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_18.json @@ -7,5 +7,6 @@ "domain": "72n2x7x0.ztb0s51", "id": "00000004-0000-0006-0000-000800000006" }, - "team": "00000001-0000-0002-0000-000000000007" + "team": "00000001-0000-0002-0000-000000000007", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_19.json b/libs/wire-api/test/golden/testObject_Contact_user_19.json index 7cf17bc4f5..799bff0386 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_19.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_19.json @@ -7,5 +7,6 @@ "domain": "h664l.dio6", "id": "00000005-0000-0003-0000-000700000007" }, - "team": "00000006-0000-0004-0000-000000000003" + "team": "00000006-0000-0004-0000-000000000003", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_2.json b/libs/wire-api/test/golden/testObject_Contact_user_2.json index 4ad15e595a..16f8a446e6 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_2.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_2.json @@ -7,5 +7,6 @@ "domain": "z.l--66-i8g8a9", "id": "00000006-0000-0004-0000-000100000007" }, - "team": "00000002-0000-0008-0000-000400000002" + "team": "00000002-0000-0008-0000-000400000002", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_20.json b/libs/wire-api/test/golden/testObject_Contact_user_20.json index 079817785a..0021c4f0b6 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_20.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_20.json @@ -7,5 +7,6 @@ "domain": "pam223.b6", "id": "00000000-0000-0000-0000-000500000001" }, - "team": null + "team": null, + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_3.json b/libs/wire-api/test/golden/testObject_Contact_user_3.json index e8cb5a2eee..f2d9c1d64b 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_3.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_3.json @@ -7,5 +7,6 @@ "domain": "h.y-2k71.rh", "id": "00000005-0000-0003-0000-000700000003" }, - "team": "00000006-0000-0005-0000-000700000008" + "team": "00000006-0000-0005-0000-000700000008", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_4.json b/libs/wire-api/test/golden/testObject_Contact_user_4.json index 13b1b0c89a..c9f77f4eaa 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_4.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_4.json @@ -7,5 +7,6 @@ "domain": "2347.cye2i7.sn.r2z83.d03", "id": "00000003-0000-0002-0000-000000000004" }, - "team": "00000000-0000-0000-0000-000500000004" + "team": "00000000-0000-0000-0000-000500000004", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_5.json b/libs/wire-api/test/golden/testObject_Contact_user_5.json index 4442242a90..ac359467ef 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_5.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_5.json @@ -7,5 +7,6 @@ "domain": "v0u29n3.er", "id": "00000004-0000-0000-0000-000300000005" }, - "team": null + "team": null, + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_6.json b/libs/wire-api/test/golden/testObject_Contact_user_6.json index 9ca25db0d5..c5a1adbc52 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_6.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_6.json @@ -7,5 +7,6 @@ "domain": "6k.p", "id": "00000003-0000-0001-0000-000400000000" }, - "team": "00000005-0000-0004-0000-000600000000" + "team": "00000005-0000-0004-0000-000600000000", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_7.json b/libs/wire-api/test/golden/testObject_Contact_user_7.json index df0499cc7e..093a117d6f 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_7.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_7.json @@ -7,5 +7,6 @@ "domain": "yr.e1-d", "id": "00000001-0000-0002-0000-000800000008" }, - "team": "00000008-0000-0001-0000-000400000008" + "team": "00000008-0000-0001-0000-000400000008", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_8.json b/libs/wire-api/test/golden/testObject_Contact_user_8.json index 67ad84dfe7..be0997d3be 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_8.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_8.json @@ -7,5 +7,6 @@ "domain": "51r9in-k6i5l8-7y6.t205p-gl2", "id": "00000002-0000-0002-0000-000600000008" }, - "team": "00000001-0000-0007-0000-000500000002" + "team": "00000001-0000-0007-0000-000500000002", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_9.json b/libs/wire-api/test/golden/testObject_Contact_user_9.json index 9318c77233..862db1f6de 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_9.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_9.json @@ -7,5 +7,6 @@ "domain": "37-p6v67.g", "id": "00000000-0000-0000-0000-000600000008" }, - "team": "00000005-0000-0002-0000-000500000000" + "team": "00000005-0000-0002-0000-000500000000", + "type": "regular" } diff --git a/libs/wire-api/test/golden/testObject_SearchResultContact_1.json b/libs/wire-api/test/golden/testObject_SearchResultContact_1.json index 3dfe76acce..78b27a72f0 100644 --- a/libs/wire-api/test/golden/testObject_SearchResultContact_1.json +++ b/libs/wire-api/test/golden/testObject_SearchResultContact_1.json @@ -9,7 +9,8 @@ "domain": "example.com", "id": "00000018-0000-0020-0000-000e00000002" }, - "team": "00000018-0000-0020-0000-000e00000002" + "team": "00000018-0000-0020-0000-000e00000002", + "type": "regular" }, { "accent_id": null, @@ -20,7 +21,8 @@ "domain": "another.example.com", "id": "00000018-0000-0020-0000-000e00000003" }, - "team": null + "team": null, + "type": "regular" } ], "found": 2, diff --git a/libs/wire-api/test/golden/testObject_SearchResultContact_2.json b/libs/wire-api/test/golden/testObject_SearchResultContact_2.json index 01117c7003..875534f79a 100644 --- a/libs/wire-api/test/golden/testObject_SearchResultContact_2.json +++ b/libs/wire-api/test/golden/testObject_SearchResultContact_2.json @@ -9,7 +9,8 @@ "domain": "example.com", "id": "00000018-0000-0020-0000-000e00000002" }, - "team": "00000018-0000-0020-0000-000e00000002" + "team": "00000018-0000-0020-0000-000e00000002", + "type": "regular" }, { "accent_id": null, @@ -20,7 +21,8 @@ "domain": "another.example.com", "id": "00000018-0000-0020-0000-000e00000003" }, - "team": null + "team": null, + "type": "regular" } ], "found": 2, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_11.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_11.json index e373317287..b05e65f929 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_11.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_11.json @@ -9,7 +9,8 @@ "domain": "bza.j", "id": "00000000-0000-0000-0000-000000000001" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": 0, @@ -20,7 +21,8 @@ "domain": "zwv.u6-f", "id": "00000001-0000-0000-0000-000000000001" }, - "team": null + "team": null, + "type": "regular" } ], "found": -1, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_13.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_13.json index 01c6c1eee8..1148666947 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_13.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_13.json @@ -9,7 +9,8 @@ "domain": "795n1zf6-he8-97ur4w.o7r---053", "id": "00000000-0000-0001-0000-000000000000" }, - "team": "00000000-0000-0000-0000-000100000000" + "team": "00000000-0000-0000-0000-000100000000", + "type": "regular" }, { "accent_id": 0, @@ -20,7 +21,8 @@ "domain": "v-t6qc.e.so7jqwv", "id": "00000000-0000-0001-0000-000000000000" }, - "team": "00000000-0000-0001-0000-000000000001" + "team": "00000000-0000-0001-0000-000000000001", + "type": "regular" }, { "accent_id": 0, @@ -31,7 +33,8 @@ "domain": "335.a3.p49c--e-fjz337", "id": "00000000-0000-0000-0000-000100000001" }, - "team": "00000000-0000-0001-0000-000000000001" + "team": "00000000-0000-0001-0000-000000000001", + "type": "regular" }, { "accent_id": null, @@ -42,7 +45,8 @@ "domain": "g.g3n", "id": "00000001-0000-0000-0000-000000000000" }, - "team": null + "team": null, + "type": "regular" } ], "found": 3, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_14.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_14.json index 7e80395ae6..daf4f793c8 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_14.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_14.json @@ -9,7 +9,8 @@ "domain": "c00y0ks9-6.q", "id": "00000000-0000-0001-0000-000100000000" }, - "team": "00000000-0000-0000-0000-000000000000" + "team": "00000000-0000-0000-0000-000000000000", + "type": "regular" }, { "accent_id": null, @@ -20,7 +21,8 @@ "domain": "g.44.s3dq77", "id": "00000001-0000-0001-0000-000000000001" }, - "team": null + "team": null, + "type": "regular" } ], "found": 1, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_19.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_19.json index c34194c529..2c656608d4 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_19.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_19.json @@ -9,7 +9,8 @@ "domain": "5de.v-6", "id": "00000000-0000-0001-0000-000100000001" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": null, @@ -20,7 +21,8 @@ "domain": "z76.kcuxql-9", "id": "00000000-0000-0001-0000-000100000000" }, - "team": "00000000-0000-0000-0000-000000000001" + "team": "00000000-0000-0000-0000-000000000001", + "type": "regular" } ], "found": 4, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_20.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_20.json index 471a9a11c8..f5fe22a975 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_20.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_20.json @@ -9,7 +9,8 @@ "domain": "66h.j", "id": "00000000-0000-0001-0000-000000000000" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": 0, @@ -20,7 +21,8 @@ "domain": "7s.k881-q-42", "id": "00000000-0000-0000-0000-000100000000" }, - "team": "00000000-0000-0000-0000-000100000001" + "team": "00000000-0000-0000-0000-000100000001", + "type": "regular" }, { "accent_id": 0, @@ -31,7 +33,8 @@ "domain": "1ux.dy", "id": "00000001-0000-0001-0000-000100000001" }, - "team": "00000000-0000-0001-0000-000000000001" + "team": "00000000-0000-0001-0000-000000000001", + "type": "regular" }, { "accent_id": null, @@ -42,7 +45,8 @@ "domain": "o.xi", "id": "00000001-0000-0000-0000-000000000000" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": null, @@ -53,7 +57,8 @@ "domain": "x5c.v", "id": "00000000-0000-0001-0000-000100000000" }, - "team": "00000001-0000-0000-0000-000000000000" + "team": "00000001-0000-0000-0000-000000000000", + "type": "regular" }, { "accent_id": 0, @@ -64,7 +69,8 @@ "domain": "9p-8z5.i", "id": "00000001-0000-0000-0000-000100000001" }, - "team": "00000001-0000-0001-0000-000000000001" + "team": "00000001-0000-0001-0000-000000000001", + "type": "regular" }, { "accent_id": 0, @@ -75,7 +81,8 @@ "domain": "h1t7.9.j492", "id": "00000000-0000-0000-0000-000100000000" }, - "team": "00000000-0000-0000-0000-000000000001" + "team": "00000000-0000-0000-0000-000000000001", + "type": "regular" }, { "accent_id": 0, @@ -86,7 +93,8 @@ "domain": "p9.y", "id": "00000000-0000-0000-0000-000100000001" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": null, @@ -97,7 +105,8 @@ "domain": "saz.d0v8", "id": "00000000-0000-0000-0000-000100000001" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": 0, @@ -108,7 +117,8 @@ "domain": "gpz.28--u.1646.v5", "id": "00000000-0000-0001-0000-000000000000" }, - "team": "00000000-0000-0001-0000-000100000001" + "team": "00000000-0000-0001-0000-000100000001", + "type": "regular" }, { "accent_id": 0, @@ -119,7 +129,8 @@ "domain": "8p.5.x11-s", "id": "00000001-0000-0000-0000-000100000001" }, - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "type": "regular" }, { "accent_id": 0, @@ -130,7 +141,8 @@ "domain": "q4x5z.mwi3", "id": "00000000-0000-0001-0000-000000000001" }, - "team": "00000000-0000-0001-0000-000000000000" + "team": "00000000-0000-0001-0000-000000000000", + "type": "regular" }, { "accent_id": 0, @@ -141,7 +153,8 @@ "domain": "38.b7", "id": "00000000-0000-0000-0000-000000000000" }, - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "type": "regular" } ], "found": 7, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_3.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_3.json index d3a743390b..eb3ce536ab 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_3.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_3.json @@ -9,7 +9,8 @@ "domain": "guh.e", "id": "00000001-0000-0001-0000-000100000000" }, - "team": "00000000-0000-0000-0000-000100000000" + "team": "00000000-0000-0000-0000-000100000000", + "type": "regular" } ], "found": 4, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_4.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_4.json index b635210594..5118eee5c3 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_4.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_4.json @@ -9,7 +9,8 @@ "domain": "2.60--1n1.ds", "id": "00000000-0000-0000-0000-000000000000" }, - "team": "00000001-0000-0000-0000-000100000001" + "team": "00000001-0000-0000-0000-000100000001", + "type": "regular" }, { "accent_id": null, @@ -20,7 +21,8 @@ "domain": "onrg.u", "id": "00000000-0000-0000-0000-000100000001" }, - "team": "00000000-0000-0000-0000-000100000000" + "team": "00000000-0000-0000-0000-000100000000", + "type": "regular" }, { "accent_id": 0, @@ -31,7 +33,8 @@ "domain": "660.v1.8z2.a-4dv.y", "id": "00000001-0000-0000-0000-000000000000" }, - "team": "00000000-0000-0001-0000-000100000001" + "team": "00000000-0000-0001-0000-000100000001", + "type": "regular" }, { "accent_id": null, @@ -42,7 +45,8 @@ "domain": "t102d9m3.tb-dryc9.ws300w5xc4", "id": "00000001-0000-0001-0000-000000000001" }, - "team": null + "team": null, + "type": "regular" }, { "accent_id": 0, @@ -53,7 +57,8 @@ "domain": "54up.l8h-b-g-i.x-c.9-7.we35781l0b", "id": "00000000-0000-0000-0000-000100000000" }, - "team": "00000000-0000-0001-0000-000000000000" + "team": "00000000-0000-0001-0000-000000000000", + "type": "regular" }, { "accent_id": 0, @@ -64,7 +69,8 @@ "domain": "a.h9-1", "id": "00000000-0000-0001-0000-000100000000" }, - "team": "00000000-0000-0000-0000-000000000000" + "team": "00000000-0000-0000-0000-000000000000", + "type": "regular" } ], "found": -5, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_5.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_5.json index c958b20589..318d927d81 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_5.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_5.json @@ -9,7 +9,8 @@ "domain": "1b-y90e265f.l-c", "id": "00000001-0000-0000-0000-000000000001" }, - "team": "00000001-0000-0000-0000-000100000001" + "team": "00000001-0000-0000-0000-000100000001", + "type": "regular" } ], "found": -6, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_7.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_7.json index 4ec4137a7f..a978842e1b 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_7.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_7.json @@ -9,7 +9,8 @@ "domain": "1386---3-nddry.o", "id": "00000001-0000-0000-0000-000000000000" }, - "team": "00000001-0000-0000-0000-000000000001" + "team": "00000001-0000-0000-0000-000000000001", + "type": "regular" }, { "accent_id": 0, @@ -20,7 +21,8 @@ "domain": "j-cz923pu.l6.73-6.qq05n.4ig.dl3", "id": "00000001-0000-0000-0000-000100000001" }, - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "type": "regular" } ], "found": 7, diff --git a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_8.json b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_8.json index e2881a4de6..302b38265d 100644 --- a/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_8.json +++ b/libs/wire-api/test/golden/testObject_SearchResult_20Contact_user_8.json @@ -9,7 +9,8 @@ "domain": "6n.n08ejr-a", "id": "00000001-0000-0001-0000-000100000001" }, - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "type": "regular" } ], "found": -7, From 800dd6f7d6fb3d6f49d39416f794dd2d3ccce1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Tue, 30 Dec 2025 16:40:22 +0200 Subject: [PATCH 04/29] Add field user types: `Contact`, `UserDoc` --- libs/wire-api/src/Wire/API/User.hs | 1 + libs/wire-api/src/Wire/API/User/Search.hs | 6 +++-- .../src/Wire/UserSearch/Types.hs | 7 +++-- .../src/Wire/UserStore/IndexUser.hs | 9 +++++-- .../src/Wire/UserSubsystem/Interpreter.hs | 26 +++++++++++++------ services/brig/src/Brig/User/API/Handle.hs | 3 ++- services/brig/src/Brig/User/Search/Index.hs | 8 ++++++ .../brig/src/Brig/User/Search/SearchIndex.hs | 3 ++- 8 files changed, 47 insertions(+), 16 deletions(-) diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index 138ded4356..b0cd98453b 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -472,6 +472,7 @@ instance (1 <= max) => ToJSON (LimitedQualifiedUserIdList max) where data UserType = UserTypeRegular | UserTypeApp | UserTypeBot deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UserType) + deriving (A.FromJSON, A.ToJSON) via (Schema UserType) instance Default UserType where def = UserTypeRegular diff --git a/libs/wire-api/src/Wire/API/User/Search.hs b/libs/wire-api/src/Wire/API/User/Search.hs index b071d3695b..b8af40f984 100644 --- a/libs/wire-api/src/Wire/API/User/Search.hs +++ b/libs/wire-api/src/Wire/API/User/Search.hs @@ -59,7 +59,7 @@ import Imports import Servant.API (FromHttpApiData, ToHttpApiData (..)) import Web.Internal.HttpApiData (parseQueryParam) import Wire.API.Team.Role (Role) -import Wire.API.User (ManagedBy) +import Wire.API.User (ManagedBy, UserType) import Wire.API.User.Identity (EmailAddress) import Wire.Arbitrary (Arbitrary, GenericUniform (..)) @@ -145,7 +145,8 @@ data Contact = Contact contactName :: Text, contactColorId :: Maybe Int, contactHandle :: Maybe Text, - contactTeam :: Maybe TeamId + contactTeam :: Maybe TeamId, + contactType :: UserType } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform Contact) @@ -161,6 +162,7 @@ instance ToSchema Contact where <*> contactColorId .= optField "accent_id" (maybeWithDefault Aeson.Null schema) <*> contactHandle .= optField "handle" (maybeWithDefault Aeson.Null schema) <*> contactTeam .= optField "team" (maybeWithDefault Aeson.Null schema) + <*> contactType .= field "type" schema -------------------------------------------------------------------------------- -- TeamContact diff --git a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs index 317a309def..756db4bb07 100644 --- a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs +++ b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs @@ -76,7 +76,8 @@ data UserDoc = UserDoc udScimExternalId :: Maybe Text, udSso :: Maybe Sso, udEmailUnvalidated :: Maybe EmailAddress, - udSearchable :: Maybe Bool + udSearchable :: Maybe Bool, + udType :: Maybe UserType } deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UserDoc) @@ -100,7 +101,8 @@ instance ToJSON UserDoc where "scim_external_id" .= udScimExternalId ud, "sso" .= udSso ud, "email_unvalidated" .= udEmailUnvalidated ud, - "searchable" .= udSearchable ud + "searchable" .= udSearchable ud, + "type" .= udType ud ] instance FromJSON UserDoc where @@ -123,6 +125,7 @@ instance FromJSON UserDoc where <*> o .:? "sso" <*> o .:? "email_unvalidated" <*> o .:? "searchable" + <*> o .:? "type" searchVisibilityInboundFieldName :: Key searchVisibilityInboundFieldName = "search_visibility_inbound" diff --git a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs index b833bbfaad..847a3e2cf7 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs @@ -154,7 +154,11 @@ indexUserToDoc searchVisInbound mRole IndexUser {..} = if shouldIndex then UserDoc - { udSearchable = value <$> searchable, + { udType = + if isJust serviceId + then Just UserTypeBot + else Nothing, -- XXX: When Nothing, it should be checked elsewhere whether this is an "app" + udSearchable = value <$> searchable, udEmailUnvalidated = value <$> unverifiedEmail, udSso = sso . value =<< ssoId, udScimExternalId = join $ scimExternalId <$> (value <$> managedBy) <*> (value <$> ssoId), @@ -214,7 +218,8 @@ normalized = transliterate (trans "Any-Latin; Latin-ASCII; Lower") emptyUserDoc :: UserId -> UserDoc emptyUserDoc uid = UserDoc - { udSearchable = Nothing, + { udType = Nothing, + udSearchable = Nothing, udEmailUnvalidated = Nothing, udSso = Nothing, udScimExternalId = Nothing, diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 940ba76352..283565a530 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -758,6 +758,7 @@ searchUsersImpl :: forall r fedM. ( Member UserStore r, Member GalleyAPIAccess r, + Member AppStore r, Member (Error UserSubsystemError) r, Member IndexedUserStore r, Member FederationConfigStore r, @@ -793,6 +794,7 @@ searchUsersImpl searcherId searchTerm maybeDomain maybeMaxResults = do searchLocally :: forall r. ( Member GalleyAPIAccess r, + Member AppStore r, Member UserStore r, Member IndexedUserStore r, Member (Input UserSubsystemConfig) r @@ -844,7 +846,11 @@ searchLocally searcher searchTerm maybeMaxResults = do contactName = maybe "" fromName userDoc.udName, contactColorId = fromIntegral . fromColourId <$> userDoc.udColourId, contactHandle = Handle.fromHandle <$> userDoc.udHandle, - contactTeam = userDoc.udTeam + contactTeam = userDoc.udTeam, + contactType = fromMaybe UserTypeRegular userDoc.udType -- default to "regular" when missing. All apps should have this set. + -- ^ XXX: For the above, to handle the legacy case of bots + -- already part of ES index, either test botness here + -- somehow, or add "requires full reindex" when deploying. } mkTeamSearchInfo :: Maybe TeamId -> Sem r TeamSearchInfo @@ -870,13 +876,17 @@ searchLocally searcher searchTerm maybeMaxResults = do (config.searchSameTeamOnly && (snd . tUnqualified $ searcher) == storedUser.teamId) || (not config.searchSameTeamOnly) if isContactVisible && fromMaybe True storedUser.searchable - then pure $ Contact - { contactQualifiedId = Qualified storedUser.id (tDomain searcher), - contactName = fromName storedUser.name, - contactHandle = Handle.fromHandle <$> storedUser.handle, - contactColorId = Just . fromIntegral . fromColourId $ storedUser.accentId, - contactTeam = storedUser.teamId - } + then do + userType <- lift $ getUserType storedUser.id storedUser.teamId storedUser.serviceId + pure $ + Contact + { contactQualifiedId = Qualified storedUser.id (tDomain searcher), + contactName = fromName storedUser.name, + contactHandle = Handle.fromHandle <$> storedUser.handle, + contactColorId = Just . fromIntegral . fromColourId $ storedUser.accentId, + contactTeam = storedUser.teamId, + contactType = userType + } else hoistMaybe Nothing searchRemotely :: diff --git a/services/brig/src/Brig/User/API/Handle.hs b/services/brig/src/Brig/User/API/Handle.hs index 0b97637255..ecad744a94 100644 --- a/services/brig/src/Brig/User/API/Handle.hs +++ b/services/brig/src/Brig/User/API/Handle.hs @@ -101,5 +101,6 @@ contactFromProfile profile = contactName = fromName $ profileName profile, contactHandle = fromHandle <$> profileHandle profile, contactColorId = Just . fromIntegral . fromColourId $ profileAccentId profile, - contactTeam = profileTeam profile + contactTeam = profileTeam profile, + contactType = profileType profile } diff --git a/services/brig/src/Brig/User/Search/Index.hs b/services/brig/src/Brig/User/Search/Index.hs index 634f059559..4c4919729d 100644 --- a/services/brig/src/Brig/User/Search/Index.hs +++ b/services/brig/src/Brig/User/Search/Index.hs @@ -466,6 +466,14 @@ indexMapping = mpIndex = True, mpAnalyzer = Nothing, mpFields = mempty + }, + "type" + .= MappingProperty + { mpType = MPKeyword, + mpStore = False, + mpIndex = True, + mpAnalyzer = Nothing, + mpFields = mempty } ] ] diff --git a/services/brig/src/Brig/User/Search/SearchIndex.hs b/services/brig/src/Brig/User/Search/SearchIndex.hs index da1458aa41..d539a85f35 100644 --- a/services/brig/src/Brig/User/Search/SearchIndex.hs +++ b/services/brig/src/Brig/User/Search/SearchIndex.hs @@ -35,7 +35,7 @@ import Data.Id import Data.Qualified (Qualified (Qualified)) import Database.Bloodhound qualified as ES import Imports hiding (log, searchable) -import Wire.API.User (ColourId (..), Name (fromName)) +import Wire.API.User (ColourId (..), Name (fromName), UserType (UserTypeRegular)) import Wire.API.User.Search import Wire.IndexedUserStore (IndexedUserStoreError (..)) import Wire.IndexedUserStore.ElasticSearch (mappingName) @@ -101,6 +101,7 @@ userDocToContact localDomain UserDoc {..} = do let contactColorId = fromIntegral . fromColourId <$> udColourId contactHandle = fromHandle <$> udHandle contactTeam = udTeam + contactType = fromMaybe UserTypeRegular udType -- default to "regular" as all app should have this field set pure $ Contact {..} -- | The default or canonical 'IndexQuery'. From dc9f457c09eeea7bc038cc2f3ca84ea30797e8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Tue, 30 Dec 2025 17:23:41 +0200 Subject: [PATCH 05/29] Sync field to Elastic Search It needs to be constructed at sync time: it's a derived field (i.e, not a field on the user table in Cassandra). --- .../IndexedUserStore/Bulk/ElasticSearch.hs | 5 +++++ .../src/Wire/UserSubsystem/Interpreter.hs | 22 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 84c137d9c3..9d66155147 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -108,6 +108,11 @@ syncAllUsersWithVersion mkVersion = mkUserDocs :: ConduitT [IndexUser] [(ES.DocId, UserDoc, ES.VersionControl)] (Sem r) () mkUserDocs = Conduit.mapM $ \page -> do + -- XXX: Fetch `type` field within this function for each user? + -- Because `IndexUser` itself doesn't have a type: it's an + -- alternative representation of the Cassandra user + -- row. `UserType` on the other hand is a derived field: whether + -- a row exists for the user in the apps table for same uid/tid. let teams :: Map TeamId [IndexUser] = Map.fromListWith (<>) $ mapMaybe (\u -> (,[u]) . value <$> u.teamId) page teamIds = Map.keys teams visMap <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 teamIds $ \t -> diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 283565a530..29a5244ea8 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -576,6 +576,7 @@ guardLockedHandleField user updateOrigin handle = do updateUserProfileImpl :: ( Member UserStore r, + Member AppStore r, Member (Error UserSubsystemError) r, Member Events r, Member GalleyAPIAccess r, @@ -640,6 +641,7 @@ updateHandleImpl :: Member GalleyAPIAccess r, Member Events r, Member UserStore r, + Member AppStore r, Member IndexedUserStore r, Member Metrics r ) => @@ -706,23 +708,27 @@ checkHandlesImpl check num = reverse <$> collectFree [] check num syncUserIndex :: forall r. ( Member UserStore r, + Member AppStore r, Member GalleyAPIAccess r, Member IndexedUserStore r, Member Metrics r ) => UserId -> Sem r () -syncUserIndex uid = do - getIndexUser uid - >>= maybe deleteFromIndex upsert +syncUserIndex uid = + getIndexUser uid >>= \case + Nothing -> deleteFromIndex + Just indexUser -> do + userType <- getUserType uid (value <$> indexUser.teamId) (value <$> indexUser.serviceId) + upsert indexUser userType where deleteFromIndex :: Sem r () deleteFromIndex = do Metrics.incCounter indexDeleteCounter IndexedUserStore.upsert (userIdToDocId uid) (emptyUserDoc uid) ES.NoVersionControl - upsert :: IndexUser -> Sem r () - upsert indexUser = do + upsert :: IndexUser -> UserType -> Sem r () + upsert indexUser userType = do vis <- maybe (pure defaultSearchVisibilityInbound) @@ -730,7 +736,8 @@ syncUserIndex uid = do indexUser.teamId tm <- maybe (pure Nothing) (selectTeamMember . value) indexUser.teamId let mRole = tm >>= mkRoleWithWriteTime - userDoc = indexUserToDoc vis (value <$> mRole) indexUser + userDoc' = indexUserToDoc vis (value <$> mRole) indexUser + userDoc = userDoc' {udType = Just userType} version = ES.ExternalGT . ES.ExternalDocVersion . docVersion $ indexUserToVersion mRole indexUser Metrics.incCounter indexUpdateCounter IndexedUserStore.upsert (userIdToDocId uid) userDoc version @@ -1056,6 +1063,7 @@ getAccountsByImpl (tSplit -> (domain, GetBy {includePendingInvitations, getByHan acceptTeamInvitationImpl :: ( Member (Input UserSubsystemConfig) r, Member UserStore r, + Member AppStore r, Member GalleyAPIAccess r, Member (Error UserSubsystemError) r, Member InvitationStore r, @@ -1120,6 +1128,7 @@ getUserExportDataImpl uid = fmap hush . runError @() $ do removeEmailEitherImpl :: ( Member UserKeyStore r, Member UserStore r, + Member AppStore r, Member Events r, Member IndexedUserStore r, Member (Input UserSubsystemConfig) r, @@ -1154,6 +1163,7 @@ checkUserIsAdminImpl uid = do setUserSearchableImpl :: ( Member UserStore r, + Member AppStore r, Member (Error UserSubsystemError) r, Member TeamSubsystem r, Member GalleyAPIAccess r, From 5e97b5228d441c8e3f7c36a50047ff0d5ce438a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Tue, 30 Dec 2025 17:34:14 +0200 Subject: [PATCH 06/29] [wip] Add field to unit tests --- .../AuthenticationSubsystem/InterpreterSpec.hs | 5 ++++- .../unit/Wire/MockInterpreters/UserStore.hs | 18 ++++++++++++------ .../test/unit/Wire/UserSearch/TypesSpec.hs | 3 ++- .../unit/Wire/UserSubsystem/InterpreterSpec.hs | 3 ++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/libs/wire-subsystems/test/unit/Wire/AuthenticationSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/AuthenticationSubsystem/InterpreterSpec.hs index 1ff35582da..dfb4329076 100644 --- a/libs/wire-subsystems/test/unit/Wire/AuthenticationSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/AuthenticationSubsystem/InterpreterSpec.hs @@ -42,6 +42,7 @@ import Wire.API.User import Wire.API.User qualified as User import Wire.API.User.Auth import Wire.API.User.Password +import Wire.AppStore import Wire.AuthenticationSubsystem import Wire.AuthenticationSubsystem.Config import Wire.AuthenticationSubsystem.Interpreter @@ -81,7 +82,8 @@ type AllEffects = EmailSubsystem, UserStore, State [StoredUser], - State (Map EmailAddress [SentMail]) + State (Map EmailAddress [SentMail]), + State [StoredApp] ] runAllEffects :: Domain -> [User] -> Maybe [Text] -> Sem AllEffects a -> Either AuthenticationSubsystemError a @@ -92,6 +94,7 @@ runAllEffects localDomain preexistingUsers mAllowedEmailDomains = local = toLocalUnsafe localDomain () } in run + . evalState mempty . evalState mempty . evalState mempty . inMemoryUserStoreInterpreter diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs index 391e8305d1..ef2bdff0db 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs @@ -31,13 +31,16 @@ import Polysemy.State import Wire.API.User hiding (DeleteUser) import Wire.API.User qualified as User import Wire.API.User.Search (SetSearchable (SetSearchable)) +import Wire.AppStore import Wire.StoredUser import Wire.UserStore import Wire.UserStore.IndexUser inMemoryUserStoreInterpreter :: forall r. - (Member (State [StoredUser]) r) => + ( Member (State [StoredUser]) r, + Member (State [StoredApp]) r + ) => InterpreterFor UserStore r inMemoryUserStoreInterpreter = interpret $ \case CreateUser new _ -> modify (newStoredUserToStoredUser new :) @@ -63,8 +66,11 @@ inMemoryUserStoreInterpreter = interpret $ \case if u.id == uid then u {emailUnvalidated = Just email} :: StoredUser else u - GetIndexUser uid -> - gets $ fmap storedUserToIndexUser . find (\user -> user.id == uid) + GetIndexUser uid -> do + mUser <- gets @[StoredUser] $ find (\user -> user.id == uid) + -- mApp <- gets @[StoredApp] $ find (\app -> app.id == uid) + -- let type_ = bool UserTypeRegular UserTypeApp $ isJust mApp + pure $ storedUserToIndexUser <$> mUser GetIndexUsersPaginated _pageSize _pagingState -> error "GetIndexUsersPaginated not implemented in inMemoryUserStoreInterpreter" UpdateUserHandleEither uid hUpdate -> runError $ modifyLocalUsers (traverse doUpdate) @@ -72,7 +78,7 @@ inMemoryUserStoreInterpreter = interpret $ \case doUpdate :: StoredUser -> Sem (Error StoredUserUpdateError : r) StoredUser doUpdate u | u.id == uid = do - handles <- gets $ mapMaybe (.handle) + handles <- gets @[StoredUser] $ mapMaybe (.handle) when ( hUpdate.old /= Just hUpdate.new @@ -87,7 +93,7 @@ inMemoryUserStoreInterpreter = interpret $ \case us <- get us' <- f us put us' - DeleteUser user -> modify $ filter (\u -> u.id /= User.userId user) + DeleteUser user -> modify @[StoredUser] $ filter (\u -> u.id /= User.userId user) LookupHandle h -> lookupHandleImpl h GlimpseHandle h -> lookupHandleImpl h LookupStatus uid -> lookupStatusImpl uid @@ -105,7 +111,7 @@ inMemoryUserStoreInterpreter = interpret $ \case doUpdate :: StoredUser -> StoredUser doUpdate u = if u.id == uid then u {email = Nothing} else u GetUserTeam uid -> do - gets $ \users -> do + gets @[StoredUser] $ \users -> do user <- find (\user -> user.id == uid) users user.teamId SetUserSearchable uid (SetSearchable searchable) -> modify $ map f diff --git a/libs/wire-subsystems/test/unit/Wire/UserSearch/TypesSpec.hs b/libs/wire-subsystems/test/unit/Wire/UserSearch/TypesSpec.hs index 4fae73f196..a09d56bd8f 100644 --- a/libs/wire-subsystems/test/unit/Wire/UserSearch/TypesSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/UserSearch/TypesSpec.hs @@ -64,7 +64,8 @@ userDoc1 = udScimExternalId = Nothing, udSso = Nothing, udEmailUnvalidated = Nothing, - udSearchable = Nothing + udSearchable = Nothing, + udType = Nothing } -- Dont touch this. This represents serialized legacy data. diff --git a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs index edfb8af1f8..0c89a3c260 100644 --- a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs @@ -1111,6 +1111,7 @@ spec = describe "UserSubsystem.Interpreter" do contactQualifiedId = Qualified searchee.id localDomain, contactName = fromName searchee.name, contactHandle = fromHandle <$> searchee.handle, - contactColorId = Just . fromIntegral $ searchee.accentId.fromColourId + contactColorId = Just . fromIntegral $ searchee.accentId.fromColourId, + contactType = UserTypeRegular } pure $ result.searchResults === [expectedContact | fromMaybe True searchee.searchable] From bbeae92881ca2ee75ef3a6c81ba1d68e0e71c21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Tue, 30 Dec 2025 19:07:12 +0200 Subject: [PATCH 07/29] Add changelog --- changelog.d/1-api-changes/add-type-field-to-contact | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1-api-changes/add-type-field-to-contact diff --git a/changelog.d/1-api-changes/add-type-field-to-contact b/changelog.d/1-api-changes/add-type-field-to-contact new file mode 100644 index 0000000000..cb58adb825 --- /dev/null +++ b/changelog.d/1-api-changes/add-type-field-to-contact @@ -0,0 +1 @@ +Add `type` field to search results received from `GET /search/contacts` \ No newline at end of file From dd62147f403280d445892040ebd58a86a1af4b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Wed, 31 Dec 2025 14:50:36 +0200 Subject: [PATCH 08/29] fixup! Add field user types: `Contact`, `UserDoc` make sanitize-pr --- libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 29a5244ea8..75341de4f1 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -855,10 +855,10 @@ searchLocally searcher searchTerm maybeMaxResults = do contactHandle = Handle.fromHandle <$> userDoc.udHandle, contactTeam = userDoc.udTeam, contactType = fromMaybe UserTypeRegular userDoc.udType -- default to "regular" when missing. All apps should have this set. - -- ^ XXX: For the above, to handle the legacy case of bots - -- already part of ES index, either test botness here - -- somehow, or add "requires full reindex" when deploying. } + -- \^ XXX: For the above, to handle the legacy case of bots + -- already part of ES index, either test botness here + -- somehow, or add "requires full reindex" when deploying. mkTeamSearchInfo :: Maybe TeamId -> Sem r TeamSearchInfo mkTeamSearchInfo searcherTeamId = do From 38e6bb032caf5431e498423f4ceec58612dcdac9 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Wed, 7 Jan 2026 13:15:51 +0100 Subject: [PATCH 09/29] Nit-pick in golden tests. --- .../golden/Test/Wire/API/Golden/Generated/Contact_user.hs | 6 +++--- libs/wire-api/test/golden/testObject_Contact_user_2.json | 2 +- libs/wire-api/test/golden/testObject_Contact_user_3.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs index 86050a826a..88b04796f6 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/Contact_user.hs @@ -22,7 +22,7 @@ import Data.Id (Id (Id)) import Data.Qualified (Qualified (Qualified, qDomain, qUnqualified)) import Data.UUID qualified as UUID (fromString) import Imports (Maybe (Just, Nothing), fromJust) -import Wire.API.User (UserType (UserTypeRegular)) +import Wire.API.User import Wire.API.User.Search (Contact (..)) testObject_Contact_user_1 :: Contact @@ -52,7 +52,7 @@ testObject_Contact_user_2 = contactColorId = Just (-5), contactHandle = Just "", contactTeam = Just (Id (fromJust (UUID.fromString "00000002-0000-0008-0000-000400000002"))), - contactType = UserTypeRegular + contactType = UserTypeApp } testObject_Contact_user_3 :: Contact @@ -67,7 +67,7 @@ testObject_Contact_user_3 = contactColorId = Just (-4), contactHandle = Just "\175177~\35955c", contactTeam = Just (Id (fromJust (UUID.fromString "00000006-0000-0005-0000-000700000008"))), - contactType = UserTypeRegular + contactType = UserTypeBot } testObject_Contact_user_4 :: Contact diff --git a/libs/wire-api/test/golden/testObject_Contact_user_2.json b/libs/wire-api/test/golden/testObject_Contact_user_2.json index 16f8a446e6..523e300330 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_2.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_2.json @@ -8,5 +8,5 @@ "id": "00000006-0000-0004-0000-000100000007" }, "team": "00000002-0000-0008-0000-000400000002", - "type": "regular" + "type": "app" } diff --git a/libs/wire-api/test/golden/testObject_Contact_user_3.json b/libs/wire-api/test/golden/testObject_Contact_user_3.json index f2d9c1d64b..d5a5f47407 100644 --- a/libs/wire-api/test/golden/testObject_Contact_user_3.json +++ b/libs/wire-api/test/golden/testObject_Contact_user_3.json @@ -8,5 +8,5 @@ "id": "00000005-0000-0003-0000-000700000003" }, "team": "00000006-0000-0005-0000-000700000008", - "type": "regular" + "type": "bot" } From b66b3f4494a3ae2343a28c158032b763792d583f Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Wed, 7 Jan 2026 13:18:14 +0100 Subject: [PATCH 10/29] typo. --- services/brig/src/Brig/User/Search/SearchIndex.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/brig/src/Brig/User/Search/SearchIndex.hs b/services/brig/src/Brig/User/Search/SearchIndex.hs index d539a85f35..694400b41a 100644 --- a/services/brig/src/Brig/User/Search/SearchIndex.hs +++ b/services/brig/src/Brig/User/Search/SearchIndex.hs @@ -101,7 +101,7 @@ userDocToContact localDomain UserDoc {..} = do let contactColorId = fromIntegral . fromColourId <$> udColourId contactHandle = fromHandle <$> udHandle contactTeam = udTeam - contactType = fromMaybe UserTypeRegular udType -- default to "regular" as all app should have this field set + contactType = fromMaybe UserTypeRegular udType -- default to "regular" as all apps should have this field set pure $ Contact {..} -- | The default or canonical 'IndexQuery'. From 5f8d00f733a2320407e1cd0e338e75f859410180 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Thu, 8 Jan 2026 12:57:03 +0100 Subject: [PATCH 11/29] Pass userType to all the right places for profile construction. There is prior art to this with email visibility and role, we're just following the pattern. There might be a nicer way, though. --- libs/wire-api/src/Wire/API/User.hs | 54 +++++++++---------- .../IndexedUserStore/Bulk/ElasticSearch.hs | 39 +++++++++++--- .../src/Wire/UserStore/IndexUser.hs | 9 ++-- .../src/Wire/UserSubsystem/Interpreter.hs | 19 ++++--- .../test/unit/Wire/MiniBackend.hs | 9 +++- .../Wire/MockInterpreters/IndexedUserStore.hs | 14 ----- .../unit/Wire/MockInterpreters/UserStore.hs | 7 +-- .../Wire/MockInterpreters/UserSubsystem.hs | 2 +- .../Wire/UserSubsystem/InterpreterSpec.hs | 20 +++++++ 9 files changed, 101 insertions(+), 72 deletions(-) diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index b0cd98453b..defa322a59 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -715,34 +715,32 @@ instance FromJSON (EmailVisibility ()) where "visible_to_self" -> pure EmailVisibleToSelf _ -> fail "unexpected value for EmailVisibility settings" -mkUserProfileWithEmail :: Maybe EmailAddress -> User -> UserLegalHoldStatus -> UserProfile -mkUserProfileWithEmail memail u legalHoldStatus = - let ty = case userService u of - Nothing -> UserTypeRegular - Just _ -> UserTypeBot - in -- This profile would be visible to any other user. When a new field is - -- added, please make sure it is OK for other users to have access to it. - UserProfile - { profileQualifiedId = userQualifiedId u, - profileHandle = userHandle u, - profileName = userDisplayName u, - profileTextStatus = userTextStatus u, - profilePict = userPict u, - profileAssets = userAssets u, - profileAccentId = userAccentId u, - profileService = userService u, - profileDeleted = userDeleted u, - profileExpire = userExpire u, - profileTeam = userTeam u, - profileEmail = memail, - profileLegalholdStatus = legalHoldStatus, - profileSupportedProtocols = userSupportedProtocols u, - profileType = ty, - profileSearchable = userSearchable u - } +-- | Create profile, overwriting the email field. Called `mkUserProfile`. +mkUserProfileWithEmail :: Maybe EmailAddress -> UserType -> User -> UserLegalHoldStatus -> UserProfile +mkUserProfileWithEmail memail userType u legalHoldStatus = + -- This profile would be visible to any other user. When a new field is + -- added, please make sure it is OK for other users to have access to it. + UserProfile + { profileQualifiedId = userQualifiedId u, + profileHandle = userHandle u, + profileName = userDisplayName u, + profileTextStatus = userTextStatus u, + profilePict = userPict u, + profileAssets = userAssets u, + profileAccentId = userAccentId u, + profileService = userService u, + profileDeleted = userDeleted u, + profileExpire = userExpire u, + profileTeam = userTeam u, + profileEmail = memail, + profileLegalholdStatus = legalHoldStatus, + profileSupportedProtocols = userSupportedProtocols u, + profileType = userType, + profileSearchable = userSearchable u + } -mkUserProfile :: EmailVisibilityConfigWithViewer -> User -> UserLegalHoldStatus -> UserProfile -mkUserProfile emailVisibilityConfigAndViewer u legalHoldStatus = +mkUserProfile :: EmailVisibilityConfigWithViewer -> UserType -> User -> UserLegalHoldStatus -> UserProfile +mkUserProfile emailVisibilityConfigAndViewer userType u legalHoldStatus = let isEmailVisible = case emailVisibilityConfigAndViewer of EmailVisibleToSelf -> False EmailVisibleIfOnTeam -> isJust (userTeam u) @@ -750,7 +748,7 @@ mkUserProfile emailVisibilityConfigAndViewer u legalHoldStatus = EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership)) -> Just viewerTeamId == userTeam u && TeamMember.hasPermission viewerMembership TeamMember.ViewSameTeamEmails - in mkUserProfileWithEmail (if isEmailVisible then userEmail u else Nothing) u legalHoldStatus + in mkUserProfileWithEmail (if isEmailVisible then userEmail u else Nothing) userType u legalHoldStatus -------------------------------------------------------------------------------- -- NewUser diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 9d66155147..6bc00795fc 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -34,6 +34,8 @@ import System.Logger.Message qualified as Log import Wire.API.Team.Feature import Wire.API.Team.Member.Info import Wire.API.Team.Role +import Wire.API.User +import Wire.AppStore import Wire.GalleyAPIAccess import Wire.IndexedUserStore (IndexedUserStore) import Wire.IndexedUserStore qualified as IndexedUserStore @@ -49,6 +51,7 @@ import Wire.UserStore.IndexUser interpretIndexedUserStoreBulk :: ( Member TinyLog r, Member UserStore r, + Member AppStore r, Member (Concurrency Unsafe) r, Member GalleyAPIAccess r, Member IndexedUserStore r, @@ -64,6 +67,7 @@ interpretIndexedUserStoreBulk = interpret \case syncAllUsersImpl :: forall r. ( Member UserStore r, + Member AppStore r, Member TinyLog r, Member (Concurrency 'Unsafe) r, Member GalleyAPIAccess r, @@ -75,6 +79,7 @@ syncAllUsersImpl = syncAllUsersWithVersion ES.ExternalGT forceSyncAllUsersImpl :: forall r. ( Member UserStore r, + Member AppStore r, Member TinyLog r, Member (Concurrency 'Unsafe) r, Member GalleyAPIAccess r, @@ -86,6 +91,7 @@ forceSyncAllUsersImpl = syncAllUsersWithVersion ES.ExternalGTE syncAllUsersWithVersion :: forall r. ( Member UserStore r, + Member AppStore r, Member TinyLog r, Member (Concurrency 'Unsafe) r, Member GalleyAPIAccess r, @@ -108,20 +114,26 @@ syncAllUsersWithVersion mkVersion = mkUserDocs :: ConduitT [IndexUser] [(ES.DocId, UserDoc, ES.VersionControl)] (Sem r) () mkUserDocs = Conduit.mapM $ \page -> do - -- XXX: Fetch `type` field within this function for each user? - -- Because `IndexUser` itself doesn't have a type: it's an - -- alternative representation of the Cassandra user - -- row. `UserType` on the other hand is a derived field: whether - -- a row exists for the user in the apps table for same uid/tid. + -- TODO: extract team visibilities, roles and user type more efficiently sending one query per page + -- TODO: introduce type ExtendedUser (or something), which + -- contains User, Maybe Role, UserType, ..., and pass around + -- ExtendedUser. this should make the code less convoluted. let teams :: Map TeamId [IndexUser] = Map.fromListWith (<>) $ mapMaybe (\u -> (,[u]) . value <$> u.teamId) page teamIds = Map.keys teams visMap <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 teamIds $ \t -> (t,) <$> teamSearchVisibilityInbound t + userTypes :: Map UserId UserType <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 page $ \iu -> + (iu.userId,) <$> getUserType iu roles :: Map UserId (WithWritetime Role) <- fmap (Map.fromList . concat) . unsafePooledForConcurrentlyN 16 (Map.toList teams) $ \(t, us) -> do tms <- (.members) <$> selectTeamMemberInfos t (fmap (.userId) us) pure $ mapMaybe mkRoleWithWriteTime tms let vis indexUser = fromMaybe defaultSearchVisibilityInbound $ (flip Map.lookup visMap . value =<< indexUser.teamId) - mkUserDoc indexUser = indexUserToDoc (vis indexUser) ((.value) <$> Map.lookup indexUser.userId roles) indexUser + mkUserDoc indexUser = + indexUserToDoc + (vis indexUser) + (fromMaybe (error "impossible") (Map.lookup indexUser.userId userTypes)) + ((.value) <$> Map.lookup indexUser.userId roles) + indexUser mkDocVersion u = mkVersion . ES.ExternalDocVersion . docVersion $ indexUserToVersion (Map.lookup u.userId roles) u pure $ map (\u -> (userIdToDocId u.userId, mkUserDoc u, mkDocVersion u)) page @@ -142,6 +154,7 @@ migrateDataImpl :: Member (Error MigrationException) r, Member IndexedUserMigrationStore r, Member UserStore r, + Member AppStore r, Member (Concurrency Unsafe) r, Member GalleyAPIAccess r, Member TinyLog r @@ -170,3 +183,17 @@ teamSearchVisibilityInbound :: (Member GalleyAPIAccess r) => TeamId -> Sem r Sea teamSearchVisibilityInbound tid = searchVisibilityInboundFromFeatureStatus . (.status) <$> getFeatureConfigForTeam @_ @SearchVisibilityInboundConfig tid + +-- | TODO: this is duplicated code from UserSubsystem, we should probably expose it as an action there. +getUserType :: + forall r. + (Member AppStore r) => + IndexUser -> + Sem r UserType +getUserType iu = case iu.serviceId of + Just _ -> pure UserTypeBot + Nothing -> do + mmApp <- mapM (getApp iu.userId) (iu.teamId <&> (.value)) + case join mmApp of + Just _ -> pure UserTypeApp + Nothing -> pure UserTypeRegular diff --git a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs index 847a3e2cf7..7ac88be08f 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs @@ -149,15 +149,12 @@ indexUserToVersion role IndexUser {..} = const () <$$> writeTimeBumper ] -indexUserToDoc :: SearchVisibilityInbound -> Maybe Role -> IndexUser -> UserDoc -indexUserToDoc searchVisInbound mRole IndexUser {..} = +indexUserToDoc :: SearchVisibilityInbound -> UserType -> Maybe Role -> IndexUser -> UserDoc +indexUserToDoc searchVisInbound type_ mRole IndexUser {..} = if shouldIndex then UserDoc - { udType = - if isJust serviceId - then Just UserTypeBot - else Nothing, -- XXX: When Nothing, it should be checked elsewhere whether this is an "app" + { udType = Just type_, udSearchable = value <$> searchable, udEmailUnvalidated = value <$> unverifiedEmail, udSso = sso . value =<< ssoId, diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 75341de4f1..a2a599b837 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -451,11 +451,11 @@ getLocalUserProfileImpl emailVisibilityConfigWithViewer luid = do lhs :: UserLegalHoldStatus <- do teamMember <- lift $ join <$> (internalGetTeamMember storedUser.id `mapM` storedUser.teamId) pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) teamMember - let user = mkUserFromStored domain locale storedUser - usrProfile = mkUserProfile emailVisibilityConfigWithViewer user lhs userType <- lift $ getUserType storedUser.id storedUser.teamId storedUser.serviceId + let user = mkUserFromStored domain locale storedUser + usrProfile = mkUserProfile emailVisibilityConfigWithViewer userType user lhs lift $ deleteLocalIfExpired user - pure $ usrProfile {profileType = userType} + pure $ usrProfile getSelfProfileImpl :: ( Member (Input UserSubsystemConfig) r, @@ -719,24 +719,24 @@ syncUserIndex uid = getIndexUser uid >>= \case Nothing -> deleteFromIndex Just indexUser -> do - userType <- getUserType uid (value <$> indexUser.teamId) (value <$> indexUser.serviceId) - upsert indexUser userType + upsert indexUser where deleteFromIndex :: Sem r () deleteFromIndex = do Metrics.incCounter indexDeleteCounter IndexedUserStore.upsert (userIdToDocId uid) (emptyUserDoc uid) ES.NoVersionControl - upsert :: IndexUser -> UserType -> Sem r () - upsert indexUser userType = do + upsert :: IndexUser -> Sem r () + upsert indexUser = do vis <- maybe (pure defaultSearchVisibilityInbound) (teamSearchVisibilityInbound . value) indexUser.teamId tm <- maybe (pure Nothing) (selectTeamMember . value) indexUser.teamId + userType <- getUserType indexUser.userId (indexUser.teamId <&> (.value)) (indexUser.serviceId <&> (.value)) let mRole = tm >>= mkRoleWithWriteTime - userDoc' = indexUserToDoc vis (value <$> mRole) indexUser + userDoc' = indexUserToDoc vis userType (value <$> mRole) indexUser userDoc = userDoc' {udType = Just userType} version = ES.ExternalGT . ES.ExternalDocVersion . docVersion $ indexUserToVersion mRole indexUser Metrics.incCounter indexUpdateCounter @@ -1184,8 +1184,7 @@ setUserSearchableImpl luid uid searchable = do getUserType :: forall r. - ( Member AppStore r - ) => + (Member AppStore r) => UserId -> Maybe TeamId -> Maybe ServiceId -> diff --git a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs index 204fc5ed73..dc3ddc17d5 100644 --- a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs +++ b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs @@ -522,10 +522,17 @@ miniGetAllProfiles :: Sem r [UserProfile] miniGetAllProfiles = do users <- gets (.users) + apps <- gets (.apps) dom <- input pure $ map - (\u -> mkUserProfileWithEmail Nothing (mkUserFromStored dom miniLocale u) defUserLegalHoldStatus) + ( \u -> + let userType + | not . null $ filter ((== u.id) . (.id)) apps = UserTypeApp + | isJust u.serviceId = UserTypeBot + | otherwise = UserTypeRegular + in mkUserProfileWithEmail Nothing userType (mkUserFromStored dom miniLocale u) defUserLegalHoldStatus + ) users miniGetUsersByIds :: [UserId] -> MiniFederationMonad 'Brig [UserProfile] diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/IndexedUserStore.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/IndexedUserStore.hs index 85b27a86b3..f481ed7367 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/IndexedUserStore.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/IndexedUserStore.hs @@ -30,10 +30,7 @@ import Polysemy.State import Wire.API.Team.Size import Wire.API.User.Search import Wire.IndexedUserStore -import Wire.MockInterpreters.UserStore (storedUserToIndexUser) -import Wire.StoredUser import Wire.UserSearch.Types -import Wire.UserStore.IndexUser newtype OrdDocId = OrdDocId Text deriving (Show, Eq, Ord) @@ -54,17 +51,6 @@ emptyIndex = docs = mempty } -storedUserToDoc :: StoredUser -> UserDoc -storedUserToDoc user = - let indexUser = storedUserToIndexUser user - in indexUserToDoc defaultSearchVisibilityInbound Nothing indexUser - -indexFromStoredUsers :: [StoredUser] -> UserIndex -indexFromStoredUsers storedUsers = do - run . execState emptyIndex . inMemoryIndexedUserStoreInterpreter $ do - for_ storedUsers $ \storedUser -> - upsert (userIdToDocId storedUser.id) (storedUserToDoc storedUser) ES.NoVersionControl - runInMemoryIndexedUserStoreIntepreter :: InterpreterFor IndexedUserStore r runInMemoryIndexedUserStoreIntepreter = evalState emptyIndex diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs index ef2bdff0db..6e2086242c 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs @@ -31,16 +31,13 @@ import Polysemy.State import Wire.API.User hiding (DeleteUser) import Wire.API.User qualified as User import Wire.API.User.Search (SetSearchable (SetSearchable)) -import Wire.AppStore import Wire.StoredUser import Wire.UserStore import Wire.UserStore.IndexUser inMemoryUserStoreInterpreter :: forall r. - ( Member (State [StoredUser]) r, - Member (State [StoredApp]) r - ) => + (Member (State [StoredUser]) r) => InterpreterFor UserStore r inMemoryUserStoreInterpreter = interpret $ \case CreateUser new _ -> modify (newStoredUserToStoredUser new :) @@ -68,8 +65,6 @@ inMemoryUserStoreInterpreter = interpret $ \case else u GetIndexUser uid -> do mUser <- gets @[StoredUser] $ find (\user -> user.id == uid) - -- mApp <- gets @[StoredApp] $ find (\app -> app.id == uid) - -- let type_ = bool UserTypeRegular UserTypeApp $ isJust mApp pure $ storedUserToIndexUser <$> mUser GetIndexUsersPaginated _pageSize _pagingState -> error "GetIndexUsersPaginated not implemented in inMemoryUserStoreInterpreter" diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserSubsystem.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserSubsystem.hs index 71dc964440..fe979c1cf1 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserSubsystem.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserSubsystem.hs @@ -69,4 +69,4 @@ userSubsystemTestInterpreter initialUsers = SetUserSearchable {} -> error "SetUserSearchable: implement on demand (userSubsystemInterpreter)" toProfile :: User -> UserProfile -toProfile u = mkUserProfileWithEmail (userEmail u) u UserLegalHoldDisabled +toProfile u = mkUserProfileWithEmail (userEmail u) UserTypeRegular u UserLegalHoldDisabled diff --git a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs index 0c89a3c260..2965f37203 100644 --- a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs @@ -38,6 +38,7 @@ import Data.Set (insert, member, notMember) import Data.Set qualified as S import Data.String.Conversions (cs) import Data.Text.Encoding (encodeUtf8) +import Database.Bloodhound.Internal.Client qualified as ES import Imports import Polysemy import Polysemy.Error @@ -60,6 +61,7 @@ import Wire.API.User.Search import Wire.API.UserEvent import Wire.AuthenticationSubsystem.Error import Wire.DomainRegistrationStore qualified as DRS +import Wire.IndexedUserStore qualified as IU import Wire.InvitationStore (InsertInvitation, StoredInvitation) import Wire.InvitationStore qualified as InvitationStore import Wire.MiniBackend @@ -67,6 +69,8 @@ import Wire.MockInterpreters import Wire.RateLimit import Wire.StoredUser import Wire.UserKeyStore +import Wire.UserSearch.Types +import Wire.UserStore.IndexUser import Wire.UserSubsystem import Wire.UserSubsystem.Error import Wire.UserSubsystem.HandleBlacklist @@ -100,6 +104,7 @@ spec = describe "UserSubsystem.Interpreter" do mkExpectedProfiles domain users = [ mkUserProfileWithEmail Nothing + (if isJust targetUser.serviceId then UserTypeBot else UserTypeRegular) (mkUserFromStored domain miniLocale targetUser) defUserLegalHoldStatus | targetUser <- users @@ -159,6 +164,7 @@ spec = describe "UserSubsystem.Interpreter" do in retrievedProfiles === [ mkUserProfile (fmap (const $ (,) <$> viewer.teamId <*> Just teamMember) config.emailVisibilityConfig) + (if isJust targetUser.serviceId then UserTypeBot else UserTypeRegular) (mkUserFromStored domain config.defaultLocale targetUser) defUserLegalHoldStatus ] @@ -175,6 +181,7 @@ spec = describe "UserSubsystem.Interpreter" do in retrievedProfile === [ mkUserProfile (fmap (const Nothing) config.emailVisibilityConfig) + (if isJust targetUser.serviceId then UserTypeBot else UserTypeRegular) (mkUserFromStored domain config.defaultLocale targetUser) defUserLegalHoldStatus ] @@ -1091,6 +1098,19 @@ spec = describe "UserSubsystem.Interpreter" do \(ActiveStoredUser searcheeNoHandle) (searcheeHandle :: Handle) (ActiveStoredUser searcher) localDomain configBase -> let teamMember = mkTeamMember searcher.id fullPermissions Nothing defUserLegalHoldStatus searchee = searcheeNoHandle {handle = Just searcheeHandle} :: StoredUser + + storedUserToDoc :: StoredUser -> UserDoc + storedUserToDoc user = + let indexUser = storedUserToIndexUser user + userType = if isJust user.serviceId then UserTypeBot else UserTypeRegular + in indexUserToDoc defaultSearchVisibilityInbound userType Nothing indexUser + + indexFromStoredUsers :: [StoredUser] -> UserIndex + indexFromStoredUsers storedUsers = do + run . execState emptyIndex . inMemoryIndexedUserStoreInterpreter $ do + for_ storedUsers $ \storedUser -> + IU.upsert (userIdToDocId storedUser.id) (storedUserToDoc storedUser) ES.NoVersionControl + localBackend = def { users = [searchee, searcher], From dbafad29e65b0382fc93ff32ebd0163a62d3e94a Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Thu, 8 Jan 2026 13:56:40 +0100 Subject: [PATCH 12/29] Connect brig re-index executable to postgres. --- .../1-api-changes/add-get-app-endpoint | 4 ++- libs/wire-api/test/unit/Test/Wire/API/User.hs | 12 ++++---- services/brig/src/Brig/Index/Eval.hs | 30 +++++++++++++------ services/brig/src/Brig/Index/Options.hs | 24 +++++++++++---- services/brig/src/Brig/Provider/API.hs | 2 +- services/brig/test/integration/API/Search.hs | 8 +++-- .../integration/Test/Federator/IngressSpec.hs | 2 +- .../integration/Test/Federator/InwardSpec.hs | 2 +- 8 files changed, 56 insertions(+), 28 deletions(-) diff --git a/changelog.d/1-api-changes/add-get-app-endpoint b/changelog.d/1-api-changes/add-get-app-endpoint index e433e9cc22..b90068db8f 100644 --- a/changelog.d/1-api-changes/add-get-app-endpoint +++ b/changelog.d/1-api-changes/add-get-app-endpoint @@ -1 +1,3 @@ -Add "get app" endpoint to Brig (`GET /teams/:tid/apps/:id`) \ No newline at end of file +Add "get app" endpoint to Brig (`GET /teams/:tid/apps/:id`) + +NB: Elastic Search re-indexing requires postgres access now! diff --git a/libs/wire-api/test/unit/Test/Wire/API/User.hs b/libs/wire-api/test/unit/Test/Wire/API/User.hs index 11ce3d914e..684a5f9fb8 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/User.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/User.hs @@ -63,7 +63,7 @@ testEmailVisibleToSelf :: TestTree testEmailVisibleToSelf = testProperty "should not contain email when email visibility is EmailVisibleToSelf" $ \user lhStatus -> - let profile = mkUserProfile EmailVisibleToSelf user lhStatus + let profile = mkUserProfile EmailVisibleToSelf UserTypeRegular user lhStatus in profileEmail profile === Nothing .&&. profileLegalholdStatus profile === lhStatus @@ -71,7 +71,7 @@ testEmailVisibleIfOnTeam :: TestTree testEmailVisibleIfOnTeam = testProperty "should contain email only if the user has one and is part of a team when email visibility is EmailVisibleIfOnTeam" $ \user lhStatus -> - let profile = mkUserProfile EmailVisibleIfOnTeam user lhStatus + let profile = mkUserProfile EmailVisibleIfOnTeam UserTypeRegular user lhStatus in (profileEmail profile === (userTeam user *> userEmail user)) .&&. profileLegalholdStatus profile === lhStatus @@ -81,13 +81,13 @@ testEmailVisibleIfOnSameTeam = where testNoViewerTeam = testProperty "should not contain email when viewer is not part of a team" $ \user lhStatus -> - let profile = mkUserProfile (EmailVisibleIfOnSameTeam Nothing) user lhStatus + let profile = mkUserProfile (EmailVisibleIfOnSameTeam Nothing) UserTypeRegular user lhStatus in (profileEmail profile === Nothing) .&&. profileLegalholdStatus profile === lhStatus testViewerDifferentTeam = testProperty "should not contain email when viewer is not part of the same team" $ \viewerTeamId viewerMembership user lhStatus -> - let profile = mkUserProfile (EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership))) user lhStatus + let profile = mkUserProfile (EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership))) UserTypeRegular user lhStatus in Just viewerTeamId /= userTeam user ==> ( profileEmail profile === Nothing .&&. profileLegalholdStatus profile === lhStatus @@ -97,7 +97,7 @@ testEmailVisibleIfOnSameTeam = \viewerTeamId (viewerMembershipNoRole :: TeamMember) userNoTeam lhStatus -> let user = userNoTeam {userTeam = Just viewerTeamId} viewerMembership = viewerMembershipNoRole & TeamMember.permissions .~ TeamMember.rolePermissions RoleExternalPartner - profile = mkUserProfile (EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership))) user lhStatus + profile = mkUserProfile (EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership))) UserTypeRegular user lhStatus in ( profileEmail profile === Nothing .&&. profileLegalholdStatus profile === lhStatus ) @@ -106,7 +106,7 @@ testEmailVisibleIfOnSameTeam = \viewerTeamId (viewerMembershipNoRole :: TeamMember) viewerRole userNoTeam lhStatus -> let user = userNoTeam {userTeam = Just viewerTeamId} viewerMembership = viewerMembershipNoRole & TeamMember.permissions .~ TeamMember.rolePermissions viewerRole - profile = mkUserProfile (EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership))) user lhStatus + profile = mkUserProfile (EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership))) UserTypeRegular user lhStatus in viewerRole /= RoleExternalPartner ==> ( profileEmail profile === userEmail user .&&. profileLegalholdStatus profile === lhStatus diff --git a/services/brig/src/Brig/Index/Eval.hs b/services/brig/src/Brig/Index/Eval.hs index dbc6a74f95..6f8dc1b1e4 100644 --- a/services/brig/src/Brig/Index/Eval.hs +++ b/services/brig/src/Brig/Index/Eval.hs @@ -39,16 +39,21 @@ import Data.Credentials (Credentials (..)) import Data.Id import Database.Bloodhound qualified as ES import Database.Bloodhound.Internal.Client (BHEnv (..)) +import Hasql.Pool +import Hasql.Pool.Extended import Imports import Polysemy import Polysemy.Embed (runEmbedded) import Polysemy.Error +import Polysemy.Input import Polysemy.TinyLog hiding (Logger) import System.Logger qualified as Log import System.Logger.Class (Logger) -import Util.Options (initCredentials) +import Util.Options import Wire.API.Federation.Client (FederatorClient) import Wire.API.Federation.Error +import Wire.AppStore +import Wire.AppStore.Postgres import Wire.BlockListStore (BlockListStore) import Wire.BlockListStore.Cassandra import Wire.FederationAPIAccess @@ -86,6 +91,7 @@ type BrigIndexEffectStack = FederationAPIAccess FederatorClient, Error FederationError, UserStore, + AppStore, IndexedUserStore, Error IndexedUserStoreError, IndexedUserMigrationStore, @@ -97,15 +103,18 @@ type BrigIndexEffectStack = Metrics, TinyLog, Concurrency 'Unsafe, + Input Pool, + Error UsageError, Embed IO, Final IO ] -runSem :: ESConnectionSettings -> CassandraSettings -> Endpoint -> Logger -> Sem BrigIndexEffectStack a -> IO a -runSem esConn cas galleyEndpoint logger action = do +runSem :: ESConnectionSettings -> CassandraSettings -> PostgresSettings -> Endpoint -> Logger -> Sem BrigIndexEffectStack a -> IO a +runSem esConn cas pg galleyEndpoint logger action = do mgr <- initHttpManagerWithTLSConfig esConn.esInsecureSkipVerifyTls esConn.esCaCert mEsCreds :: Maybe Credentials <- for esConn.esCredentials initCredentials casClient <- defInitCassandra (toCassandraOpts cas) logger + pgPool <- initPostgresPool pg.pool pg.settings pg.password let bhEnv = BHEnv { bhServer = toESServer esConn.esServer, @@ -125,6 +134,8 @@ runSem esConn cas galleyEndpoint logger action = do migrationIndexName = fromMaybe defaultMigrationIndexName (esMigrationIndexName esConn) runFinal . embedToFinal + . throwErrorToIOFinal @UsageError + . runInputConst pgPool . unsafelyPerformConcurrency . loggerToTinyLogReqId reqId logger . ignoreMetrics @@ -138,6 +149,7 @@ runSem esConn cas galleyEndpoint logger action = do . interpretIndexedUserMigrationStoreES bhEnv migrationIndexName . throwErrorToIOFinal @IndexedUserStoreError . interpretIndexedUserStoreES indexedUserStoreConfig + . interpretAppStoreToPostgres . interpretUserStoreCassandra casClient . throwErrorToIOFinal @FederationError . noFederationAPIAccess @@ -161,17 +173,17 @@ runCommand l = \case Reset es galley -> do e <- initIndex l (es ^. esConnection) galley runIndexIO e $ resetIndex (mkCreateIndexSettings es) - Reindex es cas galley -> do - runSem (es ^. esConnection) cas galley l $ + Reindex es cas pg galley -> do + runSem (es ^. esConnection) cas pg galley l $ IndexedUserStoreBulk.syncAllUsers - ReindexSameOrNewer es cas galley -> do - runSem (es ^. esConnection) cas galley l $ + ReindexSameOrNewer es cas pg galley -> do + runSem (es ^. esConnection) cas pg galley l $ IndexedUserStoreBulk.forceSyncAllUsers UpdateMapping esConn galley -> do e <- initIndex l esConn galley runIndexIO e updateMapping - Migrate es cas galley -> do - runSem (es ^. esConnection) cas galley l $ + Migrate es cas pg galley -> do + runSem (es ^. esConnection) cas pg galley l $ IndexedUserStoreBulk.migrateData ReindexFromAnotherIndex reindexSettings -> do mgr <- diff --git a/services/brig/src/Brig/Index/Options.hs b/services/brig/src/Brig/Index/Options.hs index f72db00304..67b5907022 100644 --- a/services/brig/src/Brig/Index/Options.hs +++ b/services/brig/src/Brig/Index/Options.hs @@ -29,6 +29,7 @@ module Brig.Index.Options esIndexRefreshInterval, esDeleteTemplate, CassandraSettings, + PostgresSettings (..), toCassandraOpts, cHost, cPort, @@ -54,6 +55,7 @@ import Data.Text qualified as Text import Data.Text.Strict.Lens import Data.Time.Clock (NominalDiffTime) import Database.Bloodhound qualified as ES +import Hasql.Pool.Extended import Imports import Options.Applicative import URI.ByteString @@ -63,11 +65,11 @@ import Util.Options (CassandraOpts (..), Endpoint (..), FilePathSecrets) data Command = Create ElasticSettings Endpoint | Reset ElasticSettings Endpoint - | Reindex ElasticSettings CassandraSettings Endpoint - | ReindexSameOrNewer ElasticSettings CassandraSettings Endpoint + | Reindex ElasticSettings CassandraSettings PostgresSettings Endpoint + | ReindexSameOrNewer ElasticSettings CassandraSettings PostgresSettings Endpoint | -- | 'ElasticSettings' has shards and other settings that are not needed here. UpdateMapping ESConnectionSettings Endpoint - | Migrate ElasticSettings CassandraSettings Endpoint + | Migrate ElasticSettings CassandraSettings PostgresSettings Endpoint | ReindexFromAnotherIndex ReindexFromAnotherIndexSettings deriving (Show) @@ -98,6 +100,13 @@ data CassandraSettings = CassandraSettings } deriving (Show) +data PostgresSettings = PostgresSettings + { pool :: PoolConfig, + settings :: Map Text Text, + password :: Maybe FilePathSecrets + } + deriving (Show) + data ReindexFromAnotherIndexSettings = ReindexFromAnotherIndexSettings { _reindexEsConnection :: ESConnectionSettings, _reindexDestIndex :: ES.IndexName, @@ -331,6 +340,9 @@ cassandraSettingsParser = ) ) +postgresSettingsParser :: Parser PostgresSettings +postgresSettingsParser = todo + reindexToAnotherIndexSettingsParser :: Parser ReindexFromAnotherIndexSettings reindexToAnotherIndexSettingsParser = ReindexFromAnotherIndexSettings @@ -394,19 +406,19 @@ commandParser = <> command "reindex" ( info - (Reindex <$> elasticSettingsParser <*> cassandraSettingsParser <*> galleyEndpointParser) + (Reindex <$> elasticSettingsParser <*> cassandraSettingsParser <*> postgresSettingsParser <*> galleyEndpointParser) (progDesc "Reindex all users from Cassandra if there is a new version.") ) <> command "reindex-if-same-or-newer" ( info - (ReindexSameOrNewer <$> elasticSettingsParser <*> cassandraSettingsParser <*> galleyEndpointParser) + (ReindexSameOrNewer <$> elasticSettingsParser <*> cassandraSettingsParser <*> postgresSettingsParser <*> galleyEndpointParser) (progDesc "Reindex all users from Cassandra, even if the version has not changed.") ) <> command "migrate-data" ( info - (Migrate <$> elasticSettingsParser <*> cassandraSettingsParser <*> galleyEndpointParser) + (Migrate <$> elasticSettingsParser <*> cassandraSettingsParser <*> postgresSettingsParser <*> galleyEndpointParser) (progDesc "Migrate data in elastic search") ) <> command diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 603aeaa5e4..86c7ffb13e 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -898,7 +898,7 @@ guardConvAdmin conv = do botGetSelf :: BotId -> (Handler r) Public.UserProfile botGetSelf bot = do p <- lift $ wrapClient $ User.lookupUser NoPendingInvitations (botUserId bot) - maybe (throwStd (errorToWai @'E.UserNotFound)) (\u -> pure $ Public.mkUserProfile EmailVisibleToSelf u UserLegalHoldNoConsent) p + maybe (throwStd (errorToWai @'E.UserNotFound)) (\u -> pure $ Public.mkUserProfile EmailVisibleToSelf UserTypeBot u UserLegalHoldNoConsent) p botGetClient :: (Member GalleyAPIAccess r) => BotId -> (Handler r) (Maybe Public.Client) botGetClient bot = do diff --git a/services/brig/test/integration/API/Search.hs b/services/brig/test/integration/API/Search.hs index a4809993f0..1e4a915bb4 100644 --- a/services/brig/test/integration/API/Search.hs +++ b/services/brig/test/integration/API/Search.hs @@ -40,7 +40,7 @@ import Brig.App (initHttpManagerWithTLSConfig) import Brig.Index.Eval (initIndex, runCommand) import Brig.Index.Options import Brig.Index.Options qualified as IndexOpts -import Brig.Options (ElasticSearchOpts) +import Brig.Options import Brig.Options qualified as Opt import Brig.Options qualified as Opts import Brig.User.Search.Index @@ -800,7 +800,7 @@ runReindexFromAnotherIndex logger opts newIndexName migrationIndexName = in runCommand logger $ ReindexFromAnotherIndex reindexSettings runReindexFromDatabase :: - (ElasticSettings -> CassandraSettings -> Endpoint -> Command) -> + (ElasticSettings -> CassandraSettings -> PostgresSettings -> Endpoint -> Command) -> Log.Logger -> Opt.Opts -> ES.IndexName -> @@ -824,9 +824,11 @@ runReindexFromDatabase syncCommand logger opts newIndexName migrationIndexName = & IndexOpts.cPort .~ (opts.cassandra.endpoint.port) & IndexOpts.cKeyspace .~ (C.Keyspace opts.cassandra.keyspace) ) + postgresSettings :: PostgresSettings = + PostgresSettings opts.postgresqlPool opts.postgresql opts.postgresqlPassword endpoint :: Endpoint = opts.galley - in runCommand logger $ syncCommand elasticSettings cassandraSettings endpoint + in runCommand logger $ syncCommand elasticSettings cassandraSettings postgresSettings endpoint toESConnectionSettings :: ElasticSearchOpts -> ES.IndexName -> ESConnectionSettings toESConnectionSettings opts migrationIndexName = ESConnectionSettings {..} diff --git a/services/federator/test/integration/Test/Federator/IngressSpec.hs b/services/federator/test/integration/Test/Federator/IngressSpec.hs index 41b9f42af3..be2b1c1627 100644 --- a/services/federator/test/integration/Test/Federator/IngressSpec.hs +++ b/services/federator/test/integration/Test/Federator/IngressSpec.hs @@ -59,7 +59,7 @@ spec env = do brig <- view teBrig <$> ask user <- randomUser brig - let expectedProfile = mkUserProfile EmailVisibleToSelf user UserLegalHoldNoConsent + let expectedProfile = mkUserProfile EmailVisibleToSelf UserTypeRegular user UserLegalHoldNoConsent runTestSem $ do resp <- liftToCodensity diff --git a/services/federator/test/integration/Test/Federator/InwardSpec.hs b/services/federator/test/integration/Test/Federator/InwardSpec.hs index 1daee8e77e..180d41b981 100644 --- a/services/federator/test/integration/Test/Federator/InwardSpec.hs +++ b/services/federator/test/integration/Test/Federator/InwardSpec.hs @@ -70,7 +70,7 @@ spec env = brig <- view teBrig <$> ask user <- randomUser brig - let expectedProfile = mkUserProfile EmailVisibleToSelf user UserLegalHoldNoConsent + let expectedProfile = mkUserProfile EmailVisibleToSelf UserTypeRegular user UserLegalHoldNoConsent bdy <- responseJsonError =<< inwardCall "/federation/brig/get-users-by-ids" (encode [userId user]) From 82fe92533298e1673d71d2031df58253bbb13ef0 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Thu, 8 Jan 2026 16:52:07 +0100 Subject: [PATCH 13/29] make sanitize-pr --- libs/wire-subsystems/test/unit/Wire/MiniBackend.hs | 2 +- postgres-schema.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs index dc3ddc17d5..fbd01bdd5b 100644 --- a/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs +++ b/libs/wire-subsystems/test/unit/Wire/MiniBackend.hs @@ -528,7 +528,7 @@ miniGetAllProfiles = do map ( \u -> let userType - | not . null $ filter ((== u.id) . (.id)) apps = UserTypeApp + | any ((== u.id) . (.id)) apps = UserTypeApp | isJust u.serviceId = UserTypeBot | otherwise = UserTypeRegular in mkUserProfileWithEmail Nothing userType (mkUserFromStored dom miniLocale u) defUserLegalHoldStatus diff --git a/postgres-schema.sql b/postgres-schema.sql index 378195989b..8cfc62bd5d 100644 --- a/postgres-schema.sql +++ b/postgres-schema.sql @@ -9,8 +9,8 @@ \restrict 79bbfb4630959c48307653a5cd3d83f2582b3c2210f75f10d79e3ebf0015620 --- Dumped from database version 17.6 --- Dumped by pg_dump version 17.6 +-- Dumped from database version 17.7 +-- Dumped by pg_dump version 17.7 SET statement_timeout = 0; SET lock_timeout = 0; From 9077eb0e1c3afc77ca8bd3aca8f803e131b78679 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Thu, 8 Jan 2026 17:12:59 +0100 Subject: [PATCH 14/29] Cleanup. --- .../1-api-changes/add-get-app-endpoint | 7 ++++++- libs/wire-api/src/Wire/API/User/Search.hs | 3 +-- .../src/Wire/UserSubsystem/Interpreter.hs | 21 ++++++++++--------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/changelog.d/1-api-changes/add-get-app-endpoint b/changelog.d/1-api-changes/add-get-app-endpoint index b90068db8f..f486179017 100644 --- a/changelog.d/1-api-changes/add-get-app-endpoint +++ b/changelog.d/1-api-changes/add-get-app-endpoint @@ -1,3 +1,8 @@ Add "get app" endpoint to Brig (`GET /teams/:tid/apps/:id`) -NB: Elastic Search re-indexing requires postgres access now! +Elastic Search re-indexing requires postgres access now. + +You need to do a full re-index of ElasticSearch immediately after the +upgrade. Between server upgrade and a full re-index, old-style bots +(spawned by services) may identify as be listed as regular users in +search results. diff --git a/libs/wire-api/src/Wire/API/User/Search.hs b/libs/wire-api/src/Wire/API/User/Search.hs index b8af40f984..96e01c24fe 100644 --- a/libs/wire-api/src/Wire/API/User/Search.hs +++ b/libs/wire-api/src/Wire/API/User/Search.hs @@ -42,8 +42,7 @@ import Data.Aeson hiding (object, (.=)) import Data.Aeson qualified as Aeson import Data.Attoparsec.ByteString.Char8 (string) import Data.ByteString.Char8 qualified as C8 -import Data.ByteString.Conversion -import Data.ByteString.Conversion qualified as BS +import Data.ByteString.Conversion as BS import Data.Id (TeamId, UserGroupId, UserId) import Data.Json.Util (UTCTimeMillis) import Data.OpenApi (ToParamSchema (..)) diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index a2a599b837..dee69653d0 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -716,10 +716,8 @@ syncUserIndex :: UserId -> Sem r () syncUserIndex uid = - getIndexUser uid >>= \case - Nothing -> deleteFromIndex - Just indexUser -> do - upsert indexUser + getIndexUser uid + >>= maybe deleteFromIndex upsert where deleteFromIndex :: Sem r () deleteFromIndex = do @@ -736,8 +734,7 @@ syncUserIndex uid = tm <- maybe (pure Nothing) (selectTeamMember . value) indexUser.teamId userType <- getUserType indexUser.userId (indexUser.teamId <&> (.value)) (indexUser.serviceId <&> (.value)) let mRole = tm >>= mkRoleWithWriteTime - userDoc' = indexUserToDoc vis userType (value <$> mRole) indexUser - userDoc = userDoc' {udType = Just userType} + userDoc = indexUserToDoc vis userType (value <$> mRole) indexUser version = ES.ExternalGT . ES.ExternalDocVersion . docVersion $ indexUserToVersion mRole indexUser Metrics.incCounter indexUpdateCounter IndexedUserStore.upsert (userIdToDocId uid) userDoc version @@ -854,11 +851,15 @@ searchLocally searcher searchTerm maybeMaxResults = do contactColorId = fromIntegral . fromColourId <$> userDoc.udColourId, contactHandle = Handle.fromHandle <$> userDoc.udHandle, contactTeam = userDoc.udTeam, - contactType = fromMaybe UserTypeRegular userDoc.udType -- default to "regular" when missing. All apps should have this set. + contactType = + -- NB: if you have index entries for bots that haven't + -- migrated yet, they will identify as regular users in + -- the search result. this is an accepted limitation. as + -- long as we have cassandra and elastic search involved, + -- the only way around it would be looking up serverIds in + -- cassandra for every query. + fromMaybe UserTypeRegular userDoc.udType } - -- \^ XXX: For the above, to handle the legacy case of bots - -- already part of ES index, either test botness here - -- somehow, or add "requires full reindex" when deploying. mkTeamSearchInfo :: Maybe TeamId -> Sem r TeamSearchInfo mkTeamSearchInfo searcherTeamId = do From f3aa0fde3e4d57a353d3ffc14e1416e7775847ba Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Thu, 8 Jan 2026 17:51:12 +0100 Subject: [PATCH 15/29] Concentrate userDocToContact logic in one place. The code isn't a lot shorter this way, but there aren't two places that make the same debatable decision to lie about user types of bots before ES re-index. --- .../src/Wire/UserSearch/Types.hs | 24 +++++++++++++++++ .../src/Wire/UserSubsystem/Interpreter.hs | 26 ++++++------------- .../brig/src/Brig/User/Search/SearchIndex.hs | 17 +++++------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs index 756db4bb07..f328271455 100644 --- a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs +++ b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs @@ -29,6 +29,7 @@ import Data.ByteString.Lazy import Data.Handle import Data.Id import Data.Json.Util +import Data.Qualified import Data.Text.Encoding import Database.Bloodhound.Types import Imports @@ -130,6 +131,29 @@ instance FromJSON UserDoc where searchVisibilityInboundFieldName :: Key searchVisibilityInboundFieldName = "search_visibility_inbound" +-- Qualified UserId is not included in `UserDoc`, so it needs to be +-- provided here. Monad will most likely be Identity (I promise we'll +-- always make up some name if missing) or Maybe (if no name, then no +-- contact). +userDocToContact :: (Monad m) => Qualified UserId -> (Maybe Name -> m Text) -> UserDoc -> m Contact +userDocToContact contactQualifiedId getName userDoc = + getName userDoc.udName <&> \name -> + Contact + { contactQualifiedId, + contactName = name, + contactColorId = fromIntegral . fromColourId <$> userDoc.udColourId, + contactHandle = fromHandle <$> userDoc.udHandle, + contactTeam = userDoc.udTeam, + contactType = + -- NB: if you have index entries for bots that haven't + -- migrated yet, they will identify as regular users in + -- the search result. this is an accepted limitation. as + -- long as we have cassandra and elastic search involved, + -- the only way around it would be looking up serverIds in + -- cassandra for every query. + fromMaybe UserTypeRegular userDoc.udType + } + userDocToTeamContact :: [UserGroupId] -> UserDoc -> TeamContact userDocToTeamContact userGroups UserDoc {..} = TeamContact diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index dee69653d0..5f7ba9c70f 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -827,7 +827,7 @@ searchLocally searcher searchTerm maybeMaxResults = do esMaxResults else pure $ SearchResult 0 0 0 [] FullSearch Nothing Nothing - let esContacts = map userDocToContact (searchResults esResult) + let esContacts = map userDocToContact' (searchResults esResult) -- Prepend results matching exact handle and results from ES. allContacts = case maybeExactHandleMatch of Nothing -> esContacts @@ -843,23 +843,13 @@ searchLocally searcher searchTerm maybeMaxResults = do handleTeamVisibility _ SearchVisibilityStandard = AllUsers handleTeamVisibility t SearchVisibilityNoNameOutsideTeam = TeamOnly t - userDocToContact :: UserDoc -> Contact - userDocToContact userDoc = - Contact - { contactQualifiedId = tUntagged $ qualifyAs searcher userDoc.udId, - contactName = maybe "" fromName userDoc.udName, - contactColorId = fromIntegral . fromColourId <$> userDoc.udColourId, - contactHandle = Handle.fromHandle <$> userDoc.udHandle, - contactTeam = userDoc.udTeam, - contactType = - -- NB: if you have index entries for bots that haven't - -- migrated yet, they will identify as regular users in - -- the search result. this is an accepted limitation. as - -- long as we have cassandra and elastic search involved, - -- the only way around it would be looking up serverIds in - -- cassandra for every query. - fromMaybe UserTypeRegular userDoc.udType - } + userDocToContact' :: UserDoc -> Contact + userDocToContact' userDoc = + runIdentity $ + userDocToContact + (tUntagged $ qualifyAs searcher userDoc.udId) + (Identity . maybe "" fromName) + userDoc mkTeamSearchInfo :: Maybe TeamId -> Sem r TeamSearchInfo mkTeamSearchInfo searcherTeamId = do diff --git a/services/brig/src/Brig/User/Search/SearchIndex.hs b/services/brig/src/Brig/User/Search/SearchIndex.hs index 694400b41a..41839c7a25 100644 --- a/services/brig/src/Brig/User/Search/SearchIndex.hs +++ b/services/brig/src/Brig/User/Search/SearchIndex.hs @@ -80,7 +80,7 @@ queryIndex (IndexQuery q f _) s = do r <- ES.searchByType idx mappingName search >>= ES.parseEsResponse @_ @(ES.SearchResult UserDoc) - either (throwM . IndexLookupError) (traverse (userDocToContact localDomain) . mkResult) r + either (throwM . IndexLookupError) (traverse (userDocToContact' localDomain) . mkResult) r where mkResult es = let results = mapMaybe ES.hitSource . ES.hits . ES.searchHits $ es @@ -94,15 +94,12 @@ queryIndex (IndexQuery q f _) s = do searchHasMore = Nothing } -userDocToContact :: (MonadThrow m) => Domain -> UserDoc -> m Contact -userDocToContact localDomain UserDoc {..} = do - let contactQualifiedId = Qualified udId localDomain - contactName <- maybe (throwM $ IndexError "Name not found") (pure . fromName) udName - let contactColorId = fromIntegral . fromColourId <$> udColourId - contactHandle = fromHandle <$> udHandle - contactTeam = udTeam - contactType = fromMaybe UserTypeRegular udType -- default to "regular" as all apps should have this field set - pure $ Contact {..} + userDocToContact' :: (MonadThrow m) => Domain -> UserDoc -> m Contact -- !! + userDocToContact' localDomain userDoc = + userDocToContact + (Qualified userDoc.udId localDomain) + (maybe (throwM $ IndexError "Name not found") (pure . fromName) userDoc.udName) + userDoc -- | The default or canonical 'IndexQuery'. -- From 304cfdd650cf32193534ff1c4f2f3b4ff26e45e2 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Fri, 9 Jan 2026 12:05:13 +0100 Subject: [PATCH 16/29] Fixup --- services/brig/src/Brig/User/Search/SearchIndex.hs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/brig/src/Brig/User/Search/SearchIndex.hs b/services/brig/src/Brig/User/Search/SearchIndex.hs index 41839c7a25..b648bb0cf7 100644 --- a/services/brig/src/Brig/User/Search/SearchIndex.hs +++ b/services/brig/src/Brig/User/Search/SearchIndex.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE StrictData #-} -- This file is part of the Wire Server implementation. @@ -30,12 +29,11 @@ import Control.Lens hiding (setting, (#), (.=)) import Control.Monad.Catch (MonadThrow, throwM) import Data.Aeson.Key qualified as Key import Data.Domain (Domain) -import Data.Handle (Handle (fromHandle)) import Data.Id import Data.Qualified (Qualified (Qualified)) import Database.Bloodhound qualified as ES import Imports hiding (log, searchable) -import Wire.API.User (ColourId (..), Name (fromName), UserType (UserTypeRegular)) +import Wire.API.User (Name (fromName)) import Wire.API.User.Search import Wire.IndexedUserStore (IndexedUserStoreError (..)) import Wire.IndexedUserStore.ElasticSearch (mappingName) @@ -94,11 +92,11 @@ queryIndex (IndexQuery q f _) s = do searchHasMore = Nothing } - userDocToContact' :: (MonadThrow m) => Domain -> UserDoc -> m Contact -- !! - userDocToContact' localDomain userDoc = + userDocToContact' :: (MonadThrow m) => Domain -> UserDoc -> m Contact + userDocToContact' localDomain userDoc = do userDocToContact (Qualified userDoc.udId localDomain) - (maybe (throwM $ IndexError "Name not found") (pure . fromName) userDoc.udName) + (maybe (throwM $ IndexError "Name not found") (pure . fromName)) userDoc -- | The default or canonical 'IndexQuery'. From d45c6daa7dd70dd0ef9e93e00fd696cbef2f2970 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Fri, 9 Jan 2026 12:50:31 +0100 Subject: [PATCH 17/29] typos. --- integration/test/Test/Apps.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/test/Test/Apps.hs b/integration/test/Test/Apps.hs index 95bdf6e4cc..be6a7be337 100644 --- a/integration/test/Test/Apps.hs +++ b/integration/test/Test/Apps.hs @@ -73,7 +73,7 @@ testCreateApp = do (resp.json %. "category") `shouldMatch` "ai" -- A teamless user can't get the app - outsideUser <- randomUser OwnDomain def + outsideUser <- randomUser domain def bindResponse (getApp outsideUser tid appId) $ \resp -> do resp.status `shouldMatchInt` 403 resp.json %. "label" `shouldMatch` "app-no-permission" @@ -95,7 +95,7 @@ testCreateApp = do foundDoc %. "type" `shouldMatch` aType -- App's user is findable from /search/contacts - BrigI.refreshIndex OwnDomain + BrigI.refreshIndex domain foundUserType new.name "app" -- Owner and regular member still have the type "regular" From e8dc1eff53b086e9bc01fbef53710477e4148a0b Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Mon, 12 Jan 2026 12:57:54 +0100 Subject: [PATCH 18/29] rm red hearing. this may be a problem, but it's unrelated; deferring to https://github.com/wireapp/wire-server/pull/4944 --- integration/test/Test/Apps.hs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/integration/test/Test/Apps.hs b/integration/test/Test/Apps.hs index be6a7be337..33ffda8f03 100644 --- a/integration/test/Test/Apps.hs +++ b/integration/test/Test/Apps.hs @@ -102,10 +102,6 @@ testCreateApp = do memberName <- regularMember %. "name" & asString foundUserType memberName "regular" --- XXX: Why is owner not found? --- ownerName <- owner %. "name" & asString --- foundUserType ownerName "regular" - testRefreshAppCookie :: (HasCallStack) => App () testRefreshAppCookie = do (alice, tid, [bob]) <- createTeam OwnDomain 2 From 5a2ae40a128f603d86d1be3b2d6db364b6611e73 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Mon, 12 Jan 2026 14:12:19 +0100 Subject: [PATCH 19/29] hi ci From 663d300ef112ef01981770e79f7391019b5b3972 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Mon, 12 Jan 2026 16:31:02 +0100 Subject: [PATCH 20/29] Revert dependency of brig-index from postgres. This way, the type field is already returned in the /search/contacts endpoint, but it will contain `regular` instead of `app` everywhere. The change to brig-index will happen in an upcoming PR. --- .../IndexedUserStore/Bulk/ElasticSearch.hs | 11 +++----- services/brig/src/Brig/Index/Eval.hs | 25 ++++++------------- services/brig/src/Brig/Index/Options.hs | 24 +++++------------- services/brig/test/integration/API/Search.hs | 6 ++--- 4 files changed, 20 insertions(+), 46 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 6bc00795fc..318cb9911a 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -35,7 +35,6 @@ import Wire.API.Team.Feature import Wire.API.Team.Member.Info import Wire.API.Team.Role import Wire.API.User -import Wire.AppStore import Wire.GalleyAPIAccess import Wire.IndexedUserStore (IndexedUserStore) import Wire.IndexedUserStore qualified as IndexedUserStore @@ -51,7 +50,6 @@ import Wire.UserStore.IndexUser interpretIndexedUserStoreBulk :: ( Member TinyLog r, Member UserStore r, - Member AppStore r, Member (Concurrency Unsafe) r, Member GalleyAPIAccess r, Member IndexedUserStore r, @@ -67,7 +65,6 @@ interpretIndexedUserStoreBulk = interpret \case syncAllUsersImpl :: forall r. ( Member UserStore r, - Member AppStore r, Member TinyLog r, Member (Concurrency 'Unsafe) r, Member GalleyAPIAccess r, @@ -79,7 +76,6 @@ syncAllUsersImpl = syncAllUsersWithVersion ES.ExternalGT forceSyncAllUsersImpl :: forall r. ( Member UserStore r, - Member AppStore r, Member TinyLog r, Member (Concurrency 'Unsafe) r, Member GalleyAPIAccess r, @@ -91,7 +87,6 @@ forceSyncAllUsersImpl = syncAllUsersWithVersion ES.ExternalGTE syncAllUsersWithVersion :: forall r. ( Member UserStore r, - Member AppStore r, Member TinyLog r, Member (Concurrency 'Unsafe) r, Member GalleyAPIAccess r, @@ -154,7 +149,6 @@ migrateDataImpl :: Member (Error MigrationException) r, Member IndexedUserMigrationStore r, Member UserStore r, - Member AppStore r, Member (Concurrency Unsafe) r, Member GalleyAPIAccess r, Member TinyLog r @@ -187,13 +181,16 @@ teamSearchVisibilityInbound tid = -- | TODO: this is duplicated code from UserSubsystem, we should probably expose it as an action there. getUserType :: forall r. - (Member AppStore r) => IndexUser -> Sem r UserType getUserType iu = case iu.serviceId of Just _ -> pure UserTypeBot Nothing -> do + {- + FUTUREWORK: *correct* type fields from search are coming in a separate PR mmApp <- mapM (getApp iu.userId) (iu.teamId <&> (.value)) case join mmApp of Just _ -> pure UserTypeApp Nothing -> pure UserTypeRegular + -} + pure UserTypeApp diff --git a/services/brig/src/Brig/Index/Eval.hs b/services/brig/src/Brig/Index/Eval.hs index 6f8dc1b1e4..ad58b6d53f 100644 --- a/services/brig/src/Brig/Index/Eval.hs +++ b/services/brig/src/Brig/Index/Eval.hs @@ -40,20 +40,16 @@ import Data.Id import Database.Bloodhound qualified as ES import Database.Bloodhound.Internal.Client (BHEnv (..)) import Hasql.Pool -import Hasql.Pool.Extended import Imports import Polysemy import Polysemy.Embed (runEmbedded) import Polysemy.Error -import Polysemy.Input import Polysemy.TinyLog hiding (Logger) import System.Logger qualified as Log import System.Logger.Class (Logger) import Util.Options import Wire.API.Federation.Client (FederatorClient) import Wire.API.Federation.Error -import Wire.AppStore -import Wire.AppStore.Postgres import Wire.BlockListStore (BlockListStore) import Wire.BlockListStore.Cassandra import Wire.FederationAPIAccess @@ -91,7 +87,6 @@ type BrigIndexEffectStack = FederationAPIAccess FederatorClient, Error FederationError, UserStore, - AppStore, IndexedUserStore, Error IndexedUserStoreError, IndexedUserMigrationStore, @@ -103,18 +98,16 @@ type BrigIndexEffectStack = Metrics, TinyLog, Concurrency 'Unsafe, - Input Pool, Error UsageError, Embed IO, Final IO ] -runSem :: ESConnectionSettings -> CassandraSettings -> PostgresSettings -> Endpoint -> Logger -> Sem BrigIndexEffectStack a -> IO a -runSem esConn cas pg galleyEndpoint logger action = do +runSem :: ESConnectionSettings -> CassandraSettings -> Endpoint -> Logger -> Sem BrigIndexEffectStack a -> IO a +runSem esConn cas galleyEndpoint logger action = do mgr <- initHttpManagerWithTLSConfig esConn.esInsecureSkipVerifyTls esConn.esCaCert mEsCreds :: Maybe Credentials <- for esConn.esCredentials initCredentials casClient <- defInitCassandra (toCassandraOpts cas) logger - pgPool <- initPostgresPool pg.pool pg.settings pg.password let bhEnv = BHEnv { bhServer = toESServer esConn.esServer, @@ -135,7 +128,6 @@ runSem esConn cas pg galleyEndpoint logger action = do runFinal . embedToFinal . throwErrorToIOFinal @UsageError - . runInputConst pgPool . unsafelyPerformConcurrency . loggerToTinyLogReqId reqId logger . ignoreMetrics @@ -149,7 +141,6 @@ runSem esConn cas pg galleyEndpoint logger action = do . interpretIndexedUserMigrationStoreES bhEnv migrationIndexName . throwErrorToIOFinal @IndexedUserStoreError . interpretIndexedUserStoreES indexedUserStoreConfig - . interpretAppStoreToPostgres . interpretUserStoreCassandra casClient . throwErrorToIOFinal @FederationError . noFederationAPIAccess @@ -173,17 +164,17 @@ runCommand l = \case Reset es galley -> do e <- initIndex l (es ^. esConnection) galley runIndexIO e $ resetIndex (mkCreateIndexSettings es) - Reindex es cas pg galley -> do - runSem (es ^. esConnection) cas pg galley l $ + Reindex es cas galley -> do + runSem (es ^. esConnection) cas galley l $ IndexedUserStoreBulk.syncAllUsers - ReindexSameOrNewer es cas pg galley -> do - runSem (es ^. esConnection) cas pg galley l $ + ReindexSameOrNewer es cas galley -> do + runSem (es ^. esConnection) cas galley l $ IndexedUserStoreBulk.forceSyncAllUsers UpdateMapping esConn galley -> do e <- initIndex l esConn galley runIndexIO e updateMapping - Migrate es cas pg galley -> do - runSem (es ^. esConnection) cas pg galley l $ + Migrate es cas galley -> do + runSem (es ^. esConnection) cas galley l $ IndexedUserStoreBulk.migrateData ReindexFromAnotherIndex reindexSettings -> do mgr <- diff --git a/services/brig/src/Brig/Index/Options.hs b/services/brig/src/Brig/Index/Options.hs index 67b5907022..f72db00304 100644 --- a/services/brig/src/Brig/Index/Options.hs +++ b/services/brig/src/Brig/Index/Options.hs @@ -29,7 +29,6 @@ module Brig.Index.Options esIndexRefreshInterval, esDeleteTemplate, CassandraSettings, - PostgresSettings (..), toCassandraOpts, cHost, cPort, @@ -55,7 +54,6 @@ import Data.Text qualified as Text import Data.Text.Strict.Lens import Data.Time.Clock (NominalDiffTime) import Database.Bloodhound qualified as ES -import Hasql.Pool.Extended import Imports import Options.Applicative import URI.ByteString @@ -65,11 +63,11 @@ import Util.Options (CassandraOpts (..), Endpoint (..), FilePathSecrets) data Command = Create ElasticSettings Endpoint | Reset ElasticSettings Endpoint - | Reindex ElasticSettings CassandraSettings PostgresSettings Endpoint - | ReindexSameOrNewer ElasticSettings CassandraSettings PostgresSettings Endpoint + | Reindex ElasticSettings CassandraSettings Endpoint + | ReindexSameOrNewer ElasticSettings CassandraSettings Endpoint | -- | 'ElasticSettings' has shards and other settings that are not needed here. UpdateMapping ESConnectionSettings Endpoint - | Migrate ElasticSettings CassandraSettings PostgresSettings Endpoint + | Migrate ElasticSettings CassandraSettings Endpoint | ReindexFromAnotherIndex ReindexFromAnotherIndexSettings deriving (Show) @@ -100,13 +98,6 @@ data CassandraSettings = CassandraSettings } deriving (Show) -data PostgresSettings = PostgresSettings - { pool :: PoolConfig, - settings :: Map Text Text, - password :: Maybe FilePathSecrets - } - deriving (Show) - data ReindexFromAnotherIndexSettings = ReindexFromAnotherIndexSettings { _reindexEsConnection :: ESConnectionSettings, _reindexDestIndex :: ES.IndexName, @@ -340,9 +331,6 @@ cassandraSettingsParser = ) ) -postgresSettingsParser :: Parser PostgresSettings -postgresSettingsParser = todo - reindexToAnotherIndexSettingsParser :: Parser ReindexFromAnotherIndexSettings reindexToAnotherIndexSettingsParser = ReindexFromAnotherIndexSettings @@ -406,19 +394,19 @@ commandParser = <> command "reindex" ( info - (Reindex <$> elasticSettingsParser <*> cassandraSettingsParser <*> postgresSettingsParser <*> galleyEndpointParser) + (Reindex <$> elasticSettingsParser <*> cassandraSettingsParser <*> galleyEndpointParser) (progDesc "Reindex all users from Cassandra if there is a new version.") ) <> command "reindex-if-same-or-newer" ( info - (ReindexSameOrNewer <$> elasticSettingsParser <*> cassandraSettingsParser <*> postgresSettingsParser <*> galleyEndpointParser) + (ReindexSameOrNewer <$> elasticSettingsParser <*> cassandraSettingsParser <*> galleyEndpointParser) (progDesc "Reindex all users from Cassandra, even if the version has not changed.") ) <> command "migrate-data" ( info - (Migrate <$> elasticSettingsParser <*> cassandraSettingsParser <*> postgresSettingsParser <*> galleyEndpointParser) + (Migrate <$> elasticSettingsParser <*> cassandraSettingsParser <*> galleyEndpointParser) (progDesc "Migrate data in elastic search") ) <> command diff --git a/services/brig/test/integration/API/Search.hs b/services/brig/test/integration/API/Search.hs index 1e4a915bb4..1e87cb4b7a 100644 --- a/services/brig/test/integration/API/Search.hs +++ b/services/brig/test/integration/API/Search.hs @@ -800,7 +800,7 @@ runReindexFromAnotherIndex logger opts newIndexName migrationIndexName = in runCommand logger $ ReindexFromAnotherIndex reindexSettings runReindexFromDatabase :: - (ElasticSettings -> CassandraSettings -> PostgresSettings -> Endpoint -> Command) -> + (ElasticSettings -> CassandraSettings -> Endpoint -> Command) -> Log.Logger -> Opt.Opts -> ES.IndexName -> @@ -824,11 +824,9 @@ runReindexFromDatabase syncCommand logger opts newIndexName migrationIndexName = & IndexOpts.cPort .~ (opts.cassandra.endpoint.port) & IndexOpts.cKeyspace .~ (C.Keyspace opts.cassandra.keyspace) ) - postgresSettings :: PostgresSettings = - PostgresSettings opts.postgresqlPool opts.postgresql opts.postgresqlPassword endpoint :: Endpoint = opts.galley - in runCommand logger $ syncCommand elasticSettings cassandraSettings postgresSettings endpoint + in runCommand logger $ syncCommand elasticSettings cassandraSettings endpoint toESConnectionSettings :: ElasticSearchOpts -> ES.IndexName -> ESConnectionSettings toESConnectionSettings opts migrationIndexName = ESConnectionSettings {..} From 29a61e6704349255b2144e8f8debe6b4eabc71ef Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 12:23:23 +0100 Subject: [PATCH 21/29] POC (didn't work out) add type field to cassandra user. [WIP] --- libs/wire-api/src/Wire/API/User.hs | 29 ++++++++++++++----- libs/wire-api/src/Wire/API/User/Search.hs | 4 ++- .../src/Wire/AppSubsystem/Interpreter.hs | 1 + .../IndexedUserStore/Bulk/ElasticSearch.hs | 21 -------------- libs/wire-subsystems/src/Wire/StoredUser.hs | 15 ++++++++++ .../src/Wire/UserSearch/Types.hs | 17 ++++------- .../src/Wire/UserStore/Cassandra.hs | 6 ++-- .../src/Wire/UserStore/IndexUser.hs | 21 +++++++++----- .../src/Wire/UserSubsystem/Interpreter.hs | 24 ++------------- .../unit/Wire/MockInterpreters/UserStore.hs | 1 + services/brig/brig.cabal | 1 + services/brig/src/Brig/API/User.hs | 4 +-- services/brig/src/Brig/Data/User.hs | 5 +++- services/brig/src/Brig/Provider/API.hs | 1 + services/brig/src/Brig/Schema/Run.hs | 4 ++- 15 files changed, 77 insertions(+), 77 deletions(-) diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index defa322a59..e66c38f28a 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -470,7 +470,7 @@ instance (1 <= max) => ToJSON (LimitedQualifiedUserIdList max) where -- UserType data UserType = UserTypeRegular | UserTypeApp | UserTypeBot - deriving (Eq, Show, Generic) + deriving (Eq, Ord, Bounded, Enum, Show, Generic) deriving (Arbitrary) via (GenericUniform UserType) deriving (A.FromJSON, A.ToJSON) via (Schema UserType) @@ -486,6 +486,20 @@ instance ToSchema UserType where Schema.element "bot" UserTypeBot ] +instance C.Cql UserType where + ctype = C.Tagged C.IntColumn + + fromCql (C.CqlInt i) = case i of + 0 -> pure UserTypeRegular + 1 -> pure UserTypeApp + 2 -> pure UserTypeBot + n -> Left $ "unexpected UserType: " ++ show n + fromCql _ = Left "UserType: int expected" + + toCql UserTypeRegular = C.CqlInt 0 + toCql UserTypeApp = C.CqlInt 1 + toCql UserTypeBot = C.CqlInt 2 + -------------------------------------------------------------------------------- -- UserProfile @@ -579,6 +593,7 @@ instance FromJSON SelfProfile where -- | The data of an existing user. data User = User { userQualifiedId :: Qualified UserId, + userType :: UserType, -- | User identity. For endpoints like @/self@, it will be present in the response iff -- the user is activated, and the email/phone contained in it will be guaranteedly -- verified. {#RefActivation} @@ -716,8 +731,8 @@ instance FromJSON (EmailVisibility ()) where _ -> fail "unexpected value for EmailVisibility settings" -- | Create profile, overwriting the email field. Called `mkUserProfile`. -mkUserProfileWithEmail :: Maybe EmailAddress -> UserType -> User -> UserLegalHoldStatus -> UserProfile -mkUserProfileWithEmail memail userType u legalHoldStatus = +mkUserProfileWithEmail :: Maybe EmailAddress -> User -> UserLegalHoldStatus -> UserProfile +mkUserProfileWithEmail memail u legalHoldStatus = -- This profile would be visible to any other user. When a new field is -- added, please make sure it is OK for other users to have access to it. UserProfile @@ -735,12 +750,12 @@ mkUserProfileWithEmail memail userType u legalHoldStatus = profileEmail = memail, profileLegalholdStatus = legalHoldStatus, profileSupportedProtocols = userSupportedProtocols u, - profileType = userType, + profileType = userType u, profileSearchable = userSearchable u } -mkUserProfile :: EmailVisibilityConfigWithViewer -> UserType -> User -> UserLegalHoldStatus -> UserProfile -mkUserProfile emailVisibilityConfigAndViewer userType u legalHoldStatus = +mkUserProfile :: EmailVisibilityConfigWithViewer -> User -> UserLegalHoldStatus -> UserProfile +mkUserProfile emailVisibilityConfigAndViewer u legalHoldStatus = let isEmailVisible = case emailVisibilityConfigAndViewer of EmailVisibleToSelf -> False EmailVisibleIfOnTeam -> isJust (userTeam u) @@ -748,7 +763,7 @@ mkUserProfile emailVisibilityConfigAndViewer userType u legalHoldStatus = EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership)) -> Just viewerTeamId == userTeam u && TeamMember.hasPermission viewerMembership TeamMember.ViewSameTeamEmails - in mkUserProfileWithEmail (if isEmailVisible then userEmail u else Nothing) userType u legalHoldStatus + in mkUserProfileWithEmail (if isEmailVisible then userEmail u else Nothing) u legalHoldStatus -------------------------------------------------------------------------------- -- NewUser diff --git a/libs/wire-api/src/Wire/API/User/Search.hs b/libs/wire-api/src/Wire/API/User/Search.hs index 96e01c24fe..ce135d0716 100644 --- a/libs/wire-api/src/Wire/API/User/Search.hs +++ b/libs/wire-api/src/Wire/API/User/Search.hs @@ -58,7 +58,7 @@ import Imports import Servant.API (FromHttpApiData, ToHttpApiData (..)) import Web.Internal.HttpApiData (parseQueryParam) import Wire.API.Team.Role (Role) -import Wire.API.User (ManagedBy, UserType) +import Wire.API.User (ManagedBy, UserType (..)) import Wire.API.User.Identity (EmailAddress) import Wire.Arbitrary (Arbitrary, GenericUniform (..)) @@ -185,6 +185,7 @@ instance ToSchema Sso where -- | Returned by 'browseTeam' under @/teams/:tid/search@. data TeamContact = TeamContact { teamContactUserId :: UserId, + teamContactUserType :: Maybe UserType, teamContactName :: Text, teamContactColorId :: Maybe Int, teamContactHandle :: Maybe Text, @@ -209,6 +210,7 @@ instance ToSchema TeamContact where object "TeamContact" $ TeamContact <$> teamContactUserId .= field "id" schema + <*> teamContactUserType .= maybe_ (optField "user_type" schema) <*> teamContactName .= field "name" schema <*> teamContactColorId .= optField "accent_id" (maybeWithDefault Aeson.Null schema) <*> teamContactHandle .= optField "handle" (maybeWithDefault Aeson.Null schema) diff --git a/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs index b3f22d1e1e..edcc9cb4f0 100644 --- a/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs @@ -214,6 +214,7 @@ appNewStoredUser creator new = do country = loc.lCountry, providerId = Nothing, serviceId = Nothing, + userType = Nothing, handle = Nothing, expires = Nothing, teamId = creator.teamId, diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 318cb9911a..6f099889b7 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -34,7 +34,6 @@ import System.Logger.Message qualified as Log import Wire.API.Team.Feature import Wire.API.Team.Member.Info import Wire.API.Team.Role -import Wire.API.User import Wire.GalleyAPIAccess import Wire.IndexedUserStore (IndexedUserStore) import Wire.IndexedUserStore qualified as IndexedUserStore @@ -117,8 +116,6 @@ syncAllUsersWithVersion mkVersion = teamIds = Map.keys teams visMap <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 teamIds $ \t -> (t,) <$> teamSearchVisibilityInbound t - userTypes :: Map UserId UserType <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 page $ \iu -> - (iu.userId,) <$> getUserType iu roles :: Map UserId (WithWritetime Role) <- fmap (Map.fromList . concat) . unsafePooledForConcurrentlyN 16 (Map.toList teams) $ \(t, us) -> do tms <- (.members) <$> selectTeamMemberInfos t (fmap (.userId) us) pure $ mapMaybe mkRoleWithWriteTime tms @@ -126,7 +123,6 @@ syncAllUsersWithVersion mkVersion = mkUserDoc indexUser = indexUserToDoc (vis indexUser) - (fromMaybe (error "impossible") (Map.lookup indexUser.userId userTypes)) ((.value) <$> Map.lookup indexUser.userId roles) indexUser mkDocVersion u = mkVersion . ES.ExternalDocVersion . docVersion $ indexUserToVersion (Map.lookup u.userId roles) u @@ -177,20 +173,3 @@ teamSearchVisibilityInbound :: (Member GalleyAPIAccess r) => TeamId -> Sem r Sea teamSearchVisibilityInbound tid = searchVisibilityInboundFromFeatureStatus . (.status) <$> getFeatureConfigForTeam @_ @SearchVisibilityInboundConfig tid - --- | TODO: this is duplicated code from UserSubsystem, we should probably expose it as an action there. -getUserType :: - forall r. - IndexUser -> - Sem r UserType -getUserType iu = case iu.serviceId of - Just _ -> pure UserTypeBot - Nothing -> do - {- - FUTUREWORK: *correct* type fields from search are coming in a separate PR - mmApp <- mapM (getApp iu.userId) (iu.teamId <&> (.value)) - case join mmApp of - Just _ -> pure UserTypeApp - Nothing -> pure UserTypeRegular - -} - pure UserTypeApp diff --git a/libs/wire-subsystems/src/Wire/StoredUser.hs b/libs/wire-subsystems/src/Wire/StoredUser.hs index 47edfd8c6e..64765626f1 100644 --- a/libs/wire-subsystems/src/Wire/StoredUser.hs +++ b/libs/wire-subsystems/src/Wire/StoredUser.hs @@ -39,6 +39,7 @@ import Wire.Arbitrary data StoredUser = StoredUser { id :: UserId, + userType :: Maybe UserType, name :: Name, textStatus :: Maybe TextStatus, pict :: Maybe Pict, @@ -163,6 +164,7 @@ data NewStoredUser = NewStoredUser country :: Maybe Country, providerId :: Maybe ProviderId, serviceId :: Maybe ServiceId, + userType :: Maybe UserType, handle :: Maybe Handle, teamId :: Maybe TeamId, managedBy :: ManagedBy, @@ -227,3 +229,16 @@ newStoredUserToUser (Qualified new domain) = userSupportedProtocols = new.supportedProtocols, userSearchable = new.searchable } + +-- these orphan is needed for turning {New,}StoredUser into tuples. +-- instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n, Show o, Show p, Show q, Show r, Show s, Show t, Show u) => Show (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) where +-- showsPrec _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) = +-- showChar '(' . shows a . showChar ',' . shows b . showChar ',' . shows c . showChar ',' . shows d . showChar ',' . shows e . showChar ',' . shows f . showChar ',' . shows g . showChar ',' . shows h . showChar ',' . shows i . showChar ',' . shows j . showChar ',' . shows k . showChar ',' . shows l . showChar ',' . shows m . showChar ',' . shows n . showChar ',' . shows o . showChar ',' . shows p . showChar ',' . shows q . showChar ',' . shows r . showChar ',' . shows s . showChar ',' . shows t . showChar ',' . shows u . showChar ')' + +{- + +-- these orphan is needed for turning {New,}StoredUser into tuples. +instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n, Show o, Show p, Show q, Show r, Show s, Show t, Show u, Show v) => Show (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) where + showsPrec _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) = + showChar '(' . shows a . showChar ',' . shows b . showChar ',' . shows c . showChar ',' . shows d . showChar ',' . shows e . showChar ',' . shows f . showChar ',' . shows g . showChar ',' . shows h . showChar ',' . shows i . showChar ',' . shows j . showChar ',' . shows k . showChar ',' . shows l . showChar ',' . shows m . showChar ',' . shows n . showChar ',' . shows o . showChar ',' . shows p . showChar ',' . shows q . showChar ',' . shows r . showChar ',' . shows s . showChar ',' . shows t . showChar ',' . shows u . showChar ',' . shows v . showChar ')' +-} diff --git a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs index f328271455..59616e8380 100644 --- a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs +++ b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs @@ -62,6 +62,7 @@ mkIndexVersion writetimes = -- consequently removed from the index. data UserDoc = UserDoc { udId :: UserId, + udType :: Maybe UserType, udTeam :: Maybe TeamId, udName :: Maybe Name, udNormalized :: Maybe Text, @@ -77,8 +78,7 @@ data UserDoc = UserDoc udScimExternalId :: Maybe Text, udSso :: Maybe Sso, udEmailUnvalidated :: Maybe EmailAddress, - udSearchable :: Maybe Bool, - udType :: Maybe UserType + udSearchable :: Maybe Bool } deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UserDoc) @@ -87,6 +87,7 @@ instance ToJSON UserDoc where toJSON ud = object [ "id" .= udId ud, + "user_type" .= udType ud, "team" .= udTeam ud, "name" .= udName ud, "normalized" .= udNormalized ud, @@ -110,6 +111,7 @@ instance FromJSON UserDoc where parseJSON = withObject "UserDoc" $ \o -> UserDoc <$> o .: "id" + <*> o .:? "user_type" <*> o .:? "team" <*> o .:? "name" <*> o .:? "normalized" @@ -126,7 +128,6 @@ instance FromJSON UserDoc where <*> o .:? "sso" <*> o .:? "email_unvalidated" <*> o .:? "searchable" - <*> o .:? "type" searchVisibilityInboundFieldName :: Key searchVisibilityInboundFieldName = "search_visibility_inbound" @@ -144,20 +145,14 @@ userDocToContact contactQualifiedId getName userDoc = contactColorId = fromIntegral . fromColourId <$> userDoc.udColourId, contactHandle = fromHandle <$> userDoc.udHandle, contactTeam = userDoc.udTeam, - contactType = - -- NB: if you have index entries for bots that haven't - -- migrated yet, they will identify as regular users in - -- the search result. this is an accepted limitation. as - -- long as we have cassandra and elastic search involved, - -- the only way around it would be looking up serverIds in - -- cassandra for every query. - fromMaybe UserTypeRegular userDoc.udType + contactType = fromMaybe UserTypeRegular userDoc.udType } userDocToTeamContact :: [UserGroupId] -> UserDoc -> TeamContact userDocToTeamContact userGroups UserDoc {..} = TeamContact { teamContactUserId = udId, + teamContactUserType = udType, teamContactTeam = udTeam, teamContactSso = udSso, teamContactScimExternalId = udScimExternalId, diff --git a/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs b/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs index ec2c6a85b9..adfe4a8892 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs @@ -267,8 +267,8 @@ insertUser :: PrepQuery W (TupleType NewStoredUser) () insertUser = "INSERT INTO user (id, name, text_status, picture, assets, email, sso_id, \ \accent_id, password, activated, status, expires, language, \ - \country, provider, service, handle, team, managed_by, supported_protocols, searchable) \ - \VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + \country, provider, service, user_type, handle, team, managed_by, supported_protocols, searchable) \ + \VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" insertServiceUser :: PrepQuery W (ProviderId, ServiceId, BotId, ConvId, Maybe TeamId) () insertServiceUser = @@ -285,7 +285,7 @@ selectUsers = [sql| SELECT id, name, text_status, picture, email, email_unvalidated, sso_id, accent_id, assets, activated, status, expires, language, country, provider, - service, handle, team, managed_by, supported_protocols, searchable + service, user_type, handle, team, managed_by, supported_protocols, searchable FROM user WHERE id IN ? |] diff --git a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs index 7ac88be08f..9faf6c1b29 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs @@ -44,6 +44,7 @@ data WithWritetime a = WithWriteTime {value :: a, writetime :: Writetime a} data IndexUser = IndexUser { userId :: UserId, + userType :: Maybe UserType, teamId :: Maybe (WithWritetime TeamId), name :: WithWritetime Name, accountStatus :: Maybe (WithWritetime AccountStatus), @@ -64,6 +65,7 @@ data IndexUser = IndexUser type instance TupleType IndexUser = ( UserId, + Maybe UserType, Maybe TeamId, Maybe (Writetime TeamId), Name, Writetime Name, Maybe AccountStatus, Maybe (Writetime AccountStatus), @@ -82,6 +84,7 @@ type instance instance Record IndexUser where asTuple (IndexUser {..}) = ( userId, + userType, value <$> teamId, writetime <$> teamId, name.value, name.writetime, value <$> accountStatus, writetime <$> accountStatus, @@ -99,6 +102,7 @@ instance Record IndexUser where asRecord ( u, + userType, mTeam, tTeam, name, tName, status, tStatus, @@ -114,6 +118,7 @@ instance Record IndexUser where tWriteTimeBumper ) = IndexUser { userId = u, + userType = userType, teamId = WithWriteTime <$> mTeam <*> tTeam, name = WithWriteTime name tName, accountStatus = WithWriteTime <$> status <*> tStatus, @@ -149,12 +154,13 @@ indexUserToVersion role IndexUser {..} = const () <$$> writeTimeBumper ] -indexUserToDoc :: SearchVisibilityInbound -> UserType -> Maybe Role -> IndexUser -> UserDoc -indexUserToDoc searchVisInbound type_ mRole IndexUser {..} = +indexUserToDoc :: SearchVisibilityInbound -> Maybe Role -> IndexUser -> UserDoc +indexUserToDoc searchVisInbound mRole IndexUser {..} = if shouldIndex then UserDoc - { udType = Just type_, + { udId = userId, + udType = userType, udSearchable = value <$> searchable, udEmailUnvalidated = value <$> unverifiedEmail, udSso = sso . value =<< ssoId, @@ -170,8 +176,7 @@ indexUserToDoc searchVisInbound type_ mRole IndexUser {..} = udHandle = value <$> handle, udNormalized = Just $ normalized name.value.fromName, udName = Just name.value, - udTeam = value <$> teamId, - udId = userId + udTeam = value <$> teamId } else -- We insert a tombstone-style user here, as it's easier than -- deleting the old one. It's mostly empty, but having the status here @@ -215,7 +220,8 @@ normalized = transliterate (trans "Any-Latin; Latin-ASCII; Lower") emptyUserDoc :: UserId -> UserDoc emptyUserDoc uid = UserDoc - { udType = Nothing, + { udId = uid, + udType = Nothing, udSearchable = Nothing, udEmailUnvalidated = Nothing, udSso = Nothing, @@ -231,6 +237,5 @@ emptyUserDoc uid = udHandle = Nothing, udNormalized = Nothing, udName = Nothing, - udTeam = Nothing, - udId = uid + udTeam = Nothing } diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 5f7ba9c70f..d14fa09fc7 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -451,9 +451,8 @@ getLocalUserProfileImpl emailVisibilityConfigWithViewer luid = do lhs :: UserLegalHoldStatus <- do teamMember <- lift $ join <$> (internalGetTeamMember storedUser.id `mapM` storedUser.teamId) pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) teamMember - userType <- lift $ getUserType storedUser.id storedUser.teamId storedUser.serviceId let user = mkUserFromStored domain locale storedUser - usrProfile = mkUserProfile emailVisibilityConfigWithViewer userType user lhs + usrProfile = mkUserProfile emailVisibilityConfigWithViewer user lhs lift $ deleteLocalIfExpired user pure $ usrProfile @@ -708,7 +707,6 @@ checkHandlesImpl check num = reverse <$> collectFree [] check num syncUserIndex :: forall r. ( Member UserStore r, - Member AppStore r, Member GalleyAPIAccess r, Member IndexedUserStore r, Member Metrics r @@ -732,9 +730,8 @@ syncUserIndex uid = (teamSearchVisibilityInbound . value) indexUser.teamId tm <- maybe (pure Nothing) (selectTeamMember . value) indexUser.teamId - userType <- getUserType indexUser.userId (indexUser.teamId <&> (.value)) (indexUser.serviceId <&> (.value)) let mRole = tm >>= mkRoleWithWriteTime - userDoc = indexUserToDoc vis userType (value <$> mRole) indexUser + userDoc = indexUserToDoc vis (value <$> mRole) indexUser version = ES.ExternalGT . ES.ExternalDocVersion . docVersion $ indexUserToVersion mRole indexUser Metrics.incCounter indexUpdateCounter IndexedUserStore.upsert (userIdToDocId uid) userDoc version @@ -1170,20 +1167,3 @@ setUserSearchableImpl luid uid searchable = do ensurePermissions (tUnqualified luid) tid [SetMemberSearchable] UserStore.setUserSearchable uid searchable syncUserIndex uid - --- * Helpers - -getUserType :: - forall r. - (Member AppStore r) => - UserId -> - Maybe TeamId -> - Maybe ServiceId -> - Sem r UserType -getUserType uid mTid mbServiceId = case mbServiceId of - Just _ -> pure UserTypeBot - Nothing -> do - mmApp <- mapM (getApp uid) mTid - case join mmApp of - Just _ -> pure UserTypeApp - Nothing -> pure UserTypeRegular diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs index 6e2086242c..9528396723 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs @@ -183,6 +183,7 @@ newStoredUserToStoredUser new = country = new.country, providerId = new.providerId, serviceId = new.serviceId, + userType = new.userType, handle = new.handle, teamId = new.teamId, managedBy = Just new.managedBy, diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index 67449f7bb0..b430bebf59 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -190,6 +190,7 @@ library Brig.Schema.V89_UpdateDomainRegistrationSchema Brig.Schema.V90_DomainRegistrationTeamIndex Brig.Schema.V91_UpdateDomainRegistrationSchema_AddWebappUrl + Brig.Schema.V92_AddTypeToUserSchema Brig.Team.API Brig.Team.Email Brig.Team.Template diff --git a/services/brig/src/Brig/API/User.hs b/services/brig/src/Brig/API/User.hs index 7c0397cefe..9e4bfe994f 100644 --- a/services/brig/src/Brig/API/User.hs +++ b/services/brig/src/Brig/API/User.hs @@ -213,7 +213,7 @@ createUserSpar new = do tid = newUserSparTeamId new -- Create account - account <- lift $ newStoredUser new' Nothing (Just tid) handle' + account <- lift $ newStoredUser new' Nothing (Just tid) handle' UserTypeRegular domain <- viewFederationDomain let u = newStoredUserToUser (Qualified account domain) lift $ do @@ -399,7 +399,7 @@ createUser rateLimitKey new = do traverse (liftSem . HashPassword.hashPassword8 rateLimitKey) new'.newUserPassword - newStoredUser new' {newUserPassword = mHashedPassword} mbInv tid mbHandle + newStoredUser new' {newUserPassword = mHashedPassword} mbInv tid mbHandle UserTypeRegular domain <- viewFederationDomain let u = newStoredUserToUser (Qualified account domain) let uid = account.id diff --git a/services/brig/src/Brig/Data/User.hs b/services/brig/src/Brig/Data/User.hs index 0116e5e8e2..a7232afe07 100644 --- a/services/brig/src/Brig/Data/User.hs +++ b/services/brig/src/Brig/Data/User.hs @@ -88,8 +88,9 @@ newStoredUser :: Maybe InvitationId -> Maybe TeamId -> Maybe Handle -> + UserType -> AppT r NewStoredUser -newStoredUser u inv tid mbHandle = do +newStoredUser u inv tid mbHandle userType = do defLoc <- defaultUserLocale <$> asks (.settings) uid <- Id <$> do @@ -137,6 +138,7 @@ newStoredUser u inv tid mbHandle = do country = l.lCountry, providerId = Nothing, serviceId = Nothing, + userType = Just userType, handle = mbHandle, expires = e, teamId = tid, @@ -169,6 +171,7 @@ newStoredUserViaScim uid externalId tid locale name email = do country = loc.lCountry, providerId = Nothing, serviceId = Nothing, + userType = Just UserTypeRegular, handle = Nothing, expires = Nothing, teamId = Just tid, diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 86c7ffb13e..e9041eaedf 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -826,6 +826,7 @@ addBot zuid zcon cid add = do language = locale.lLanguage, country = locale.lCountry, serviceId = Just sid, + userType = Just UserTypeBot, providerId = Just pid, handle = Nothing, teamId = Nothing, diff --git a/services/brig/src/Brig/Schema/Run.hs b/services/brig/src/Brig/Schema/Run.hs index c13d35287b..f68325fb46 100644 --- a/services/brig/src/Brig/Schema/Run.hs +++ b/services/brig/src/Brig/Schema/Run.hs @@ -66,6 +66,7 @@ import Brig.Schema.V88_DomainRegistrationTable qualified as V88_DomainRegistrati import Brig.Schema.V89_UpdateDomainRegistrationSchema qualified as V89_UpdateDomainRegistrationSchema import Brig.Schema.V90_DomainRegistrationTeamIndex qualified as V90_DomainRegistrationTeamIndex import Brig.Schema.V91_UpdateDomainRegistrationSchema_AddWebappUrl qualified as V91_UpdateDomainRegistrationSchema_AddWebappUrl +import Brig.Schema.V92_AddTypeToUserSchema qualified as V92_AddTypeToUserSchema import Cassandra.MigrateSchema (migrateSchema) import Cassandra.Schema import Control.Exception (finally) @@ -138,7 +139,8 @@ migrations = V88_DomainRegistrationTable.migration, V89_UpdateDomainRegistrationSchema.migration, V90_DomainRegistrationTeamIndex.migration, - V91_UpdateDomainRegistrationSchema_AddWebappUrl.migration + V91_UpdateDomainRegistrationSchema_AddWebappUrl.migration, + V92_AddTypeToUserSchema.migration -- FUTUREWORK: undo V41 (searchable flag); we stopped using it in -- https://github.com/wireapp/wire-server/pull/964 ] From 7e4586fcc389d0d1c85541a8551d92dc589c882e Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 12:24:01 +0100 Subject: [PATCH 22/29] Revert "POC (didn't work out) add type field to cassandra user. [WIP]" This reverts commit 738c4592573e179e694f6dd5f9b07be8837eaa2b. --- libs/wire-api/src/Wire/API/User.hs | 29 +++++-------------- libs/wire-api/src/Wire/API/User/Search.hs | 4 +-- .../src/Wire/AppSubsystem/Interpreter.hs | 1 - .../IndexedUserStore/Bulk/ElasticSearch.hs | 21 ++++++++++++++ libs/wire-subsystems/src/Wire/StoredUser.hs | 15 ---------- .../src/Wire/UserSearch/Types.hs | 17 +++++++---- .../src/Wire/UserStore/Cassandra.hs | 6 ++-- .../src/Wire/UserStore/IndexUser.hs | 21 +++++--------- .../src/Wire/UserSubsystem/Interpreter.hs | 24 +++++++++++++-- .../unit/Wire/MockInterpreters/UserStore.hs | 1 - services/brig/brig.cabal | 1 - services/brig/src/Brig/API/User.hs | 4 +-- services/brig/src/Brig/Data/User.hs | 5 +--- services/brig/src/Brig/Provider/API.hs | 1 - services/brig/src/Brig/Schema/Run.hs | 4 +-- 15 files changed, 77 insertions(+), 77 deletions(-) diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index e66c38f28a..defa322a59 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -470,7 +470,7 @@ instance (1 <= max) => ToJSON (LimitedQualifiedUserIdList max) where -- UserType data UserType = UserTypeRegular | UserTypeApp | UserTypeBot - deriving (Eq, Ord, Bounded, Enum, Show, Generic) + deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UserType) deriving (A.FromJSON, A.ToJSON) via (Schema UserType) @@ -486,20 +486,6 @@ instance ToSchema UserType where Schema.element "bot" UserTypeBot ] -instance C.Cql UserType where - ctype = C.Tagged C.IntColumn - - fromCql (C.CqlInt i) = case i of - 0 -> pure UserTypeRegular - 1 -> pure UserTypeApp - 2 -> pure UserTypeBot - n -> Left $ "unexpected UserType: " ++ show n - fromCql _ = Left "UserType: int expected" - - toCql UserTypeRegular = C.CqlInt 0 - toCql UserTypeApp = C.CqlInt 1 - toCql UserTypeBot = C.CqlInt 2 - -------------------------------------------------------------------------------- -- UserProfile @@ -593,7 +579,6 @@ instance FromJSON SelfProfile where -- | The data of an existing user. data User = User { userQualifiedId :: Qualified UserId, - userType :: UserType, -- | User identity. For endpoints like @/self@, it will be present in the response iff -- the user is activated, and the email/phone contained in it will be guaranteedly -- verified. {#RefActivation} @@ -731,8 +716,8 @@ instance FromJSON (EmailVisibility ()) where _ -> fail "unexpected value for EmailVisibility settings" -- | Create profile, overwriting the email field. Called `mkUserProfile`. -mkUserProfileWithEmail :: Maybe EmailAddress -> User -> UserLegalHoldStatus -> UserProfile -mkUserProfileWithEmail memail u legalHoldStatus = +mkUserProfileWithEmail :: Maybe EmailAddress -> UserType -> User -> UserLegalHoldStatus -> UserProfile +mkUserProfileWithEmail memail userType u legalHoldStatus = -- This profile would be visible to any other user. When a new field is -- added, please make sure it is OK for other users to have access to it. UserProfile @@ -750,12 +735,12 @@ mkUserProfileWithEmail memail u legalHoldStatus = profileEmail = memail, profileLegalholdStatus = legalHoldStatus, profileSupportedProtocols = userSupportedProtocols u, - profileType = userType u, + profileType = userType, profileSearchable = userSearchable u } -mkUserProfile :: EmailVisibilityConfigWithViewer -> User -> UserLegalHoldStatus -> UserProfile -mkUserProfile emailVisibilityConfigAndViewer u legalHoldStatus = +mkUserProfile :: EmailVisibilityConfigWithViewer -> UserType -> User -> UserLegalHoldStatus -> UserProfile +mkUserProfile emailVisibilityConfigAndViewer userType u legalHoldStatus = let isEmailVisible = case emailVisibilityConfigAndViewer of EmailVisibleToSelf -> False EmailVisibleIfOnTeam -> isJust (userTeam u) @@ -763,7 +748,7 @@ mkUserProfile emailVisibilityConfigAndViewer u legalHoldStatus = EmailVisibleIfOnSameTeam (Just (viewerTeamId, viewerMembership)) -> Just viewerTeamId == userTeam u && TeamMember.hasPermission viewerMembership TeamMember.ViewSameTeamEmails - in mkUserProfileWithEmail (if isEmailVisible then userEmail u else Nothing) u legalHoldStatus + in mkUserProfileWithEmail (if isEmailVisible then userEmail u else Nothing) userType u legalHoldStatus -------------------------------------------------------------------------------- -- NewUser diff --git a/libs/wire-api/src/Wire/API/User/Search.hs b/libs/wire-api/src/Wire/API/User/Search.hs index ce135d0716..96e01c24fe 100644 --- a/libs/wire-api/src/Wire/API/User/Search.hs +++ b/libs/wire-api/src/Wire/API/User/Search.hs @@ -58,7 +58,7 @@ import Imports import Servant.API (FromHttpApiData, ToHttpApiData (..)) import Web.Internal.HttpApiData (parseQueryParam) import Wire.API.Team.Role (Role) -import Wire.API.User (ManagedBy, UserType (..)) +import Wire.API.User (ManagedBy, UserType) import Wire.API.User.Identity (EmailAddress) import Wire.Arbitrary (Arbitrary, GenericUniform (..)) @@ -185,7 +185,6 @@ instance ToSchema Sso where -- | Returned by 'browseTeam' under @/teams/:tid/search@. data TeamContact = TeamContact { teamContactUserId :: UserId, - teamContactUserType :: Maybe UserType, teamContactName :: Text, teamContactColorId :: Maybe Int, teamContactHandle :: Maybe Text, @@ -210,7 +209,6 @@ instance ToSchema TeamContact where object "TeamContact" $ TeamContact <$> teamContactUserId .= field "id" schema - <*> teamContactUserType .= maybe_ (optField "user_type" schema) <*> teamContactName .= field "name" schema <*> teamContactColorId .= optField "accent_id" (maybeWithDefault Aeson.Null schema) <*> teamContactHandle .= optField "handle" (maybeWithDefault Aeson.Null schema) diff --git a/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs index edcc9cb4f0..b3f22d1e1e 100644 --- a/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/AppSubsystem/Interpreter.hs @@ -214,7 +214,6 @@ appNewStoredUser creator new = do country = loc.lCountry, providerId = Nothing, serviceId = Nothing, - userType = Nothing, handle = Nothing, expires = Nothing, teamId = creator.teamId, diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 6f099889b7..318cb9911a 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -34,6 +34,7 @@ import System.Logger.Message qualified as Log import Wire.API.Team.Feature import Wire.API.Team.Member.Info import Wire.API.Team.Role +import Wire.API.User import Wire.GalleyAPIAccess import Wire.IndexedUserStore (IndexedUserStore) import Wire.IndexedUserStore qualified as IndexedUserStore @@ -116,6 +117,8 @@ syncAllUsersWithVersion mkVersion = teamIds = Map.keys teams visMap <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 teamIds $ \t -> (t,) <$> teamSearchVisibilityInbound t + userTypes :: Map UserId UserType <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 page $ \iu -> + (iu.userId,) <$> getUserType iu roles :: Map UserId (WithWritetime Role) <- fmap (Map.fromList . concat) . unsafePooledForConcurrentlyN 16 (Map.toList teams) $ \(t, us) -> do tms <- (.members) <$> selectTeamMemberInfos t (fmap (.userId) us) pure $ mapMaybe mkRoleWithWriteTime tms @@ -123,6 +126,7 @@ syncAllUsersWithVersion mkVersion = mkUserDoc indexUser = indexUserToDoc (vis indexUser) + (fromMaybe (error "impossible") (Map.lookup indexUser.userId userTypes)) ((.value) <$> Map.lookup indexUser.userId roles) indexUser mkDocVersion u = mkVersion . ES.ExternalDocVersion . docVersion $ indexUserToVersion (Map.lookup u.userId roles) u @@ -173,3 +177,20 @@ teamSearchVisibilityInbound :: (Member GalleyAPIAccess r) => TeamId -> Sem r Sea teamSearchVisibilityInbound tid = searchVisibilityInboundFromFeatureStatus . (.status) <$> getFeatureConfigForTeam @_ @SearchVisibilityInboundConfig tid + +-- | TODO: this is duplicated code from UserSubsystem, we should probably expose it as an action there. +getUserType :: + forall r. + IndexUser -> + Sem r UserType +getUserType iu = case iu.serviceId of + Just _ -> pure UserTypeBot + Nothing -> do + {- + FUTUREWORK: *correct* type fields from search are coming in a separate PR + mmApp <- mapM (getApp iu.userId) (iu.teamId <&> (.value)) + case join mmApp of + Just _ -> pure UserTypeApp + Nothing -> pure UserTypeRegular + -} + pure UserTypeApp diff --git a/libs/wire-subsystems/src/Wire/StoredUser.hs b/libs/wire-subsystems/src/Wire/StoredUser.hs index 64765626f1..47edfd8c6e 100644 --- a/libs/wire-subsystems/src/Wire/StoredUser.hs +++ b/libs/wire-subsystems/src/Wire/StoredUser.hs @@ -39,7 +39,6 @@ import Wire.Arbitrary data StoredUser = StoredUser { id :: UserId, - userType :: Maybe UserType, name :: Name, textStatus :: Maybe TextStatus, pict :: Maybe Pict, @@ -164,7 +163,6 @@ data NewStoredUser = NewStoredUser country :: Maybe Country, providerId :: Maybe ProviderId, serviceId :: Maybe ServiceId, - userType :: Maybe UserType, handle :: Maybe Handle, teamId :: Maybe TeamId, managedBy :: ManagedBy, @@ -229,16 +227,3 @@ newStoredUserToUser (Qualified new domain) = userSupportedProtocols = new.supportedProtocols, userSearchable = new.searchable } - --- these orphan is needed for turning {New,}StoredUser into tuples. --- instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n, Show o, Show p, Show q, Show r, Show s, Show t, Show u) => Show (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) where --- showsPrec _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) = --- showChar '(' . shows a . showChar ',' . shows b . showChar ',' . shows c . showChar ',' . shows d . showChar ',' . shows e . showChar ',' . shows f . showChar ',' . shows g . showChar ',' . shows h . showChar ',' . shows i . showChar ',' . shows j . showChar ',' . shows k . showChar ',' . shows l . showChar ',' . shows m . showChar ',' . shows n . showChar ',' . shows o . showChar ',' . shows p . showChar ',' . shows q . showChar ',' . shows r . showChar ',' . shows s . showChar ',' . shows t . showChar ',' . shows u . showChar ')' - -{- - --- these orphan is needed for turning {New,}StoredUser into tuples. -instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g, Show h, Show i, Show j, Show k, Show l, Show m, Show n, Show o, Show p, Show q, Show r, Show s, Show t, Show u, Show v) => Show (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) where - showsPrec _ (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) = - showChar '(' . shows a . showChar ',' . shows b . showChar ',' . shows c . showChar ',' . shows d . showChar ',' . shows e . showChar ',' . shows f . showChar ',' . shows g . showChar ',' . shows h . showChar ',' . shows i . showChar ',' . shows j . showChar ',' . shows k . showChar ',' . shows l . showChar ',' . shows m . showChar ',' . shows n . showChar ',' . shows o . showChar ',' . shows p . showChar ',' . shows q . showChar ',' . shows r . showChar ',' . shows s . showChar ',' . shows t . showChar ',' . shows u . showChar ',' . shows v . showChar ')' --} diff --git a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs index 59616e8380..f328271455 100644 --- a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs +++ b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs @@ -62,7 +62,6 @@ mkIndexVersion writetimes = -- consequently removed from the index. data UserDoc = UserDoc { udId :: UserId, - udType :: Maybe UserType, udTeam :: Maybe TeamId, udName :: Maybe Name, udNormalized :: Maybe Text, @@ -78,7 +77,8 @@ data UserDoc = UserDoc udScimExternalId :: Maybe Text, udSso :: Maybe Sso, udEmailUnvalidated :: Maybe EmailAddress, - udSearchable :: Maybe Bool + udSearchable :: Maybe Bool, + udType :: Maybe UserType } deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UserDoc) @@ -87,7 +87,6 @@ instance ToJSON UserDoc where toJSON ud = object [ "id" .= udId ud, - "user_type" .= udType ud, "team" .= udTeam ud, "name" .= udName ud, "normalized" .= udNormalized ud, @@ -111,7 +110,6 @@ instance FromJSON UserDoc where parseJSON = withObject "UserDoc" $ \o -> UserDoc <$> o .: "id" - <*> o .:? "user_type" <*> o .:? "team" <*> o .:? "name" <*> o .:? "normalized" @@ -128,6 +126,7 @@ instance FromJSON UserDoc where <*> o .:? "sso" <*> o .:? "email_unvalidated" <*> o .:? "searchable" + <*> o .:? "type" searchVisibilityInboundFieldName :: Key searchVisibilityInboundFieldName = "search_visibility_inbound" @@ -145,14 +144,20 @@ userDocToContact contactQualifiedId getName userDoc = contactColorId = fromIntegral . fromColourId <$> userDoc.udColourId, contactHandle = fromHandle <$> userDoc.udHandle, contactTeam = userDoc.udTeam, - contactType = fromMaybe UserTypeRegular userDoc.udType + contactType = + -- NB: if you have index entries for bots that haven't + -- migrated yet, they will identify as regular users in + -- the search result. this is an accepted limitation. as + -- long as we have cassandra and elastic search involved, + -- the only way around it would be looking up serverIds in + -- cassandra for every query. + fromMaybe UserTypeRegular userDoc.udType } userDocToTeamContact :: [UserGroupId] -> UserDoc -> TeamContact userDocToTeamContact userGroups UserDoc {..} = TeamContact { teamContactUserId = udId, - teamContactUserType = udType, teamContactTeam = udTeam, teamContactSso = udSso, teamContactScimExternalId = udScimExternalId, diff --git a/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs b/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs index adfe4a8892..ec2c6a85b9 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/Cassandra.hs @@ -267,8 +267,8 @@ insertUser :: PrepQuery W (TupleType NewStoredUser) () insertUser = "INSERT INTO user (id, name, text_status, picture, assets, email, sso_id, \ \accent_id, password, activated, status, expires, language, \ - \country, provider, service, user_type, handle, team, managed_by, supported_protocols, searchable) \ - \VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + \country, provider, service, handle, team, managed_by, supported_protocols, searchable) \ + \VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" insertServiceUser :: PrepQuery W (ProviderId, ServiceId, BotId, ConvId, Maybe TeamId) () insertServiceUser = @@ -285,7 +285,7 @@ selectUsers = [sql| SELECT id, name, text_status, picture, email, email_unvalidated, sso_id, accent_id, assets, activated, status, expires, language, country, provider, - service, user_type, handle, team, managed_by, supported_protocols, searchable + service, handle, team, managed_by, supported_protocols, searchable FROM user WHERE id IN ? |] diff --git a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs index 9faf6c1b29..7ac88be08f 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs @@ -44,7 +44,6 @@ data WithWritetime a = WithWriteTime {value :: a, writetime :: Writetime a} data IndexUser = IndexUser { userId :: UserId, - userType :: Maybe UserType, teamId :: Maybe (WithWritetime TeamId), name :: WithWritetime Name, accountStatus :: Maybe (WithWritetime AccountStatus), @@ -65,7 +64,6 @@ data IndexUser = IndexUser type instance TupleType IndexUser = ( UserId, - Maybe UserType, Maybe TeamId, Maybe (Writetime TeamId), Name, Writetime Name, Maybe AccountStatus, Maybe (Writetime AccountStatus), @@ -84,7 +82,6 @@ type instance instance Record IndexUser where asTuple (IndexUser {..}) = ( userId, - userType, value <$> teamId, writetime <$> teamId, name.value, name.writetime, value <$> accountStatus, writetime <$> accountStatus, @@ -102,7 +99,6 @@ instance Record IndexUser where asRecord ( u, - userType, mTeam, tTeam, name, tName, status, tStatus, @@ -118,7 +114,6 @@ instance Record IndexUser where tWriteTimeBumper ) = IndexUser { userId = u, - userType = userType, teamId = WithWriteTime <$> mTeam <*> tTeam, name = WithWriteTime name tName, accountStatus = WithWriteTime <$> status <*> tStatus, @@ -154,13 +149,12 @@ indexUserToVersion role IndexUser {..} = const () <$$> writeTimeBumper ] -indexUserToDoc :: SearchVisibilityInbound -> Maybe Role -> IndexUser -> UserDoc -indexUserToDoc searchVisInbound mRole IndexUser {..} = +indexUserToDoc :: SearchVisibilityInbound -> UserType -> Maybe Role -> IndexUser -> UserDoc +indexUserToDoc searchVisInbound type_ mRole IndexUser {..} = if shouldIndex then UserDoc - { udId = userId, - udType = userType, + { udType = Just type_, udSearchable = value <$> searchable, udEmailUnvalidated = value <$> unverifiedEmail, udSso = sso . value =<< ssoId, @@ -176,7 +170,8 @@ indexUserToDoc searchVisInbound mRole IndexUser {..} = udHandle = value <$> handle, udNormalized = Just $ normalized name.value.fromName, udName = Just name.value, - udTeam = value <$> teamId + udTeam = value <$> teamId, + udId = userId } else -- We insert a tombstone-style user here, as it's easier than -- deleting the old one. It's mostly empty, but having the status here @@ -220,8 +215,7 @@ normalized = transliterate (trans "Any-Latin; Latin-ASCII; Lower") emptyUserDoc :: UserId -> UserDoc emptyUserDoc uid = UserDoc - { udId = uid, - udType = Nothing, + { udType = Nothing, udSearchable = Nothing, udEmailUnvalidated = Nothing, udSso = Nothing, @@ -237,5 +231,6 @@ emptyUserDoc uid = udHandle = Nothing, udNormalized = Nothing, udName = Nothing, - udTeam = Nothing + udTeam = Nothing, + udId = uid } diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index d14fa09fc7..5f7ba9c70f 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -451,8 +451,9 @@ getLocalUserProfileImpl emailVisibilityConfigWithViewer luid = do lhs :: UserLegalHoldStatus <- do teamMember <- lift $ join <$> (internalGetTeamMember storedUser.id `mapM` storedUser.teamId) pure $ maybe defUserLegalHoldStatus (view legalHoldStatus) teamMember + userType <- lift $ getUserType storedUser.id storedUser.teamId storedUser.serviceId let user = mkUserFromStored domain locale storedUser - usrProfile = mkUserProfile emailVisibilityConfigWithViewer user lhs + usrProfile = mkUserProfile emailVisibilityConfigWithViewer userType user lhs lift $ deleteLocalIfExpired user pure $ usrProfile @@ -707,6 +708,7 @@ checkHandlesImpl check num = reverse <$> collectFree [] check num syncUserIndex :: forall r. ( Member UserStore r, + Member AppStore r, Member GalleyAPIAccess r, Member IndexedUserStore r, Member Metrics r @@ -730,8 +732,9 @@ syncUserIndex uid = (teamSearchVisibilityInbound . value) indexUser.teamId tm <- maybe (pure Nothing) (selectTeamMember . value) indexUser.teamId + userType <- getUserType indexUser.userId (indexUser.teamId <&> (.value)) (indexUser.serviceId <&> (.value)) let mRole = tm >>= mkRoleWithWriteTime - userDoc = indexUserToDoc vis (value <$> mRole) indexUser + userDoc = indexUserToDoc vis userType (value <$> mRole) indexUser version = ES.ExternalGT . ES.ExternalDocVersion . docVersion $ indexUserToVersion mRole indexUser Metrics.incCounter indexUpdateCounter IndexedUserStore.upsert (userIdToDocId uid) userDoc version @@ -1167,3 +1170,20 @@ setUserSearchableImpl luid uid searchable = do ensurePermissions (tUnqualified luid) tid [SetMemberSearchable] UserStore.setUserSearchable uid searchable syncUserIndex uid + +-- * Helpers + +getUserType :: + forall r. + (Member AppStore r) => + UserId -> + Maybe TeamId -> + Maybe ServiceId -> + Sem r UserType +getUserType uid mTid mbServiceId = case mbServiceId of + Just _ -> pure UserTypeBot + Nothing -> do + mmApp <- mapM (getApp uid) mTid + case join mmApp of + Just _ -> pure UserTypeApp + Nothing -> pure UserTypeRegular diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs index 9528396723..6e2086242c 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/UserStore.hs @@ -183,7 +183,6 @@ newStoredUserToStoredUser new = country = new.country, providerId = new.providerId, serviceId = new.serviceId, - userType = new.userType, handle = new.handle, teamId = new.teamId, managedBy = Just new.managedBy, diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index b430bebf59..67449f7bb0 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -190,7 +190,6 @@ library Brig.Schema.V89_UpdateDomainRegistrationSchema Brig.Schema.V90_DomainRegistrationTeamIndex Brig.Schema.V91_UpdateDomainRegistrationSchema_AddWebappUrl - Brig.Schema.V92_AddTypeToUserSchema Brig.Team.API Brig.Team.Email Brig.Team.Template diff --git a/services/brig/src/Brig/API/User.hs b/services/brig/src/Brig/API/User.hs index 9e4bfe994f..7c0397cefe 100644 --- a/services/brig/src/Brig/API/User.hs +++ b/services/brig/src/Brig/API/User.hs @@ -213,7 +213,7 @@ createUserSpar new = do tid = newUserSparTeamId new -- Create account - account <- lift $ newStoredUser new' Nothing (Just tid) handle' UserTypeRegular + account <- lift $ newStoredUser new' Nothing (Just tid) handle' domain <- viewFederationDomain let u = newStoredUserToUser (Qualified account domain) lift $ do @@ -399,7 +399,7 @@ createUser rateLimitKey new = do traverse (liftSem . HashPassword.hashPassword8 rateLimitKey) new'.newUserPassword - newStoredUser new' {newUserPassword = mHashedPassword} mbInv tid mbHandle UserTypeRegular + newStoredUser new' {newUserPassword = mHashedPassword} mbInv tid mbHandle domain <- viewFederationDomain let u = newStoredUserToUser (Qualified account domain) let uid = account.id diff --git a/services/brig/src/Brig/Data/User.hs b/services/brig/src/Brig/Data/User.hs index a7232afe07..0116e5e8e2 100644 --- a/services/brig/src/Brig/Data/User.hs +++ b/services/brig/src/Brig/Data/User.hs @@ -88,9 +88,8 @@ newStoredUser :: Maybe InvitationId -> Maybe TeamId -> Maybe Handle -> - UserType -> AppT r NewStoredUser -newStoredUser u inv tid mbHandle userType = do +newStoredUser u inv tid mbHandle = do defLoc <- defaultUserLocale <$> asks (.settings) uid <- Id <$> do @@ -138,7 +137,6 @@ newStoredUser u inv tid mbHandle userType = do country = l.lCountry, providerId = Nothing, serviceId = Nothing, - userType = Just userType, handle = mbHandle, expires = e, teamId = tid, @@ -171,7 +169,6 @@ newStoredUserViaScim uid externalId tid locale name email = do country = loc.lCountry, providerId = Nothing, serviceId = Nothing, - userType = Just UserTypeRegular, handle = Nothing, expires = Nothing, teamId = Just tid, diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index e9041eaedf..86c7ffb13e 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -826,7 +826,6 @@ addBot zuid zcon cid add = do language = locale.lLanguage, country = locale.lCountry, serviceId = Just sid, - userType = Just UserTypeBot, providerId = Just pid, handle = Nothing, teamId = Nothing, diff --git a/services/brig/src/Brig/Schema/Run.hs b/services/brig/src/Brig/Schema/Run.hs index f68325fb46..c13d35287b 100644 --- a/services/brig/src/Brig/Schema/Run.hs +++ b/services/brig/src/Brig/Schema/Run.hs @@ -66,7 +66,6 @@ import Brig.Schema.V88_DomainRegistrationTable qualified as V88_DomainRegistrati import Brig.Schema.V89_UpdateDomainRegistrationSchema qualified as V89_UpdateDomainRegistrationSchema import Brig.Schema.V90_DomainRegistrationTeamIndex qualified as V90_DomainRegistrationTeamIndex import Brig.Schema.V91_UpdateDomainRegistrationSchema_AddWebappUrl qualified as V91_UpdateDomainRegistrationSchema_AddWebappUrl -import Brig.Schema.V92_AddTypeToUserSchema qualified as V92_AddTypeToUserSchema import Cassandra.MigrateSchema (migrateSchema) import Cassandra.Schema import Control.Exception (finally) @@ -139,8 +138,7 @@ migrations = V88_DomainRegistrationTable.migration, V89_UpdateDomainRegistrationSchema.migration, V90_DomainRegistrationTeamIndex.migration, - V91_UpdateDomainRegistrationSchema_AddWebappUrl.migration, - V92_AddTypeToUserSchema.migration + V91_UpdateDomainRegistrationSchema_AddWebappUrl.migration -- FUTUREWORK: undo V41 (searchable flag); we stopped using it in -- https://github.com/wireapp/wire-server/pull/964 ] From 9630d53752db99d72ecdcbf49b3d7e73d2839018 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 12:30:48 +0100 Subject: [PATCH 23/29] Demote TODOs. the only debatable one is about performance in ES re-indexing, but there we already do the expensive thing for roles and team visibilities, so the effect on performance should be tolerable. --- .../src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 318cb9911a..80db88d9dd 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -109,10 +109,13 @@ syncAllUsersWithVersion mkVersion = mkUserDocs :: ConduitT [IndexUser] [(ES.DocId, UserDoc, ES.VersionControl)] (Sem r) () mkUserDocs = Conduit.mapM $ \page -> do - -- TODO: extract team visibilities, roles and user type more efficiently sending one query per page - -- TODO: introduce type ExtendedUser (or something), which + -- FUTUREWORK: extract team visibilities, roles and user type + -- more efficiently sending one query per page + + -- FUTUREWORK: introduce type ExtendedUser (or something), which -- contains User, Maybe Role, UserType, ..., and pass around -- ExtendedUser. this should make the code less convoluted. + let teams :: Map TeamId [IndexUser] = Map.fromListWith (<>) $ mapMaybe (\u -> (,[u]) . value <$> u.teamId) page teamIds = Map.keys teams visMap <- fmap Map.fromList . unsafePooledForConcurrentlyN 16 teamIds $ \t -> @@ -178,7 +181,8 @@ teamSearchVisibilityInbound tid = searchVisibilityInboundFromFeatureStatus . (.status) <$> getFeatureConfigForTeam @_ @SearchVisibilityInboundConfig tid --- | TODO: this is duplicated code from UserSubsystem, we should probably expose it as an action there. +-- | FUTUREWORK: this is duplicated code from UserSubsystem, we should +-- probably expose it as an action there. getUserType :: forall r. IndexUser -> @@ -187,7 +191,8 @@ getUserType iu = case iu.serviceId of Just _ -> pure UserTypeBot Nothing -> do {- - FUTUREWORK: *correct* type fields from search are coming in a separate PR + FUTUREWORK: *correct* type fields from search are coming in a separate PR: + mmApp <- mapM (getApp iu.userId) (iu.teamId <&> (.value)) case join mmApp of Just _ -> pure UserTypeApp From 1c097b1273297520eb0ff11b4deba5bc76f3fc3b Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 12:31:58 +0100 Subject: [PATCH 24/29] Fix typo. --- .../src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 80db88d9dd..54aa772dd8 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -198,4 +198,4 @@ getUserType iu = case iu.serviceId of Just _ -> pure UserTypeApp Nothing -> pure UserTypeRegular -} - pure UserTypeApp + pure UserTypeRegular From 7e5ab9afbbe723cd544225956a30d7c3dc9443ee Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 12:48:54 +0100 Subject: [PATCH 25/29] Cleanup, make source comment clearer (and less wrong). --- .../src/Wire/UserSearch/Types.hs | 20 +++++++++---------- .../src/Wire/UserStore/IndexUser.hs | 8 ++++---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs index f328271455..28b766f0db 100644 --- a/libs/wire-subsystems/src/Wire/UserSearch/Types.hs +++ b/libs/wire-subsystems/src/Wire/UserSearch/Types.hs @@ -62,6 +62,7 @@ mkIndexVersion writetimes = -- consequently removed from the index. data UserDoc = UserDoc { udId :: UserId, + udType :: Maybe UserType, udTeam :: Maybe TeamId, udName :: Maybe Name, udNormalized :: Maybe Text, @@ -77,8 +78,7 @@ data UserDoc = UserDoc udScimExternalId :: Maybe Text, udSso :: Maybe Sso, udEmailUnvalidated :: Maybe EmailAddress, - udSearchable :: Maybe Bool, - udType :: Maybe UserType + udSearchable :: Maybe Bool } deriving (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform UserDoc) @@ -87,6 +87,7 @@ instance ToJSON UserDoc where toJSON ud = object [ "id" .= udId ud, + "type" .= udType ud, "team" .= udTeam ud, "name" .= udName ud, "normalized" .= udNormalized ud, @@ -102,14 +103,14 @@ instance ToJSON UserDoc where "scim_external_id" .= udScimExternalId ud, "sso" .= udSso ud, "email_unvalidated" .= udEmailUnvalidated ud, - "searchable" .= udSearchable ud, - "type" .= udType ud + "searchable" .= udSearchable ud ] instance FromJSON UserDoc where parseJSON = withObject "UserDoc" $ \o -> UserDoc <$> o .: "id" + <*> o .:? "type" <*> o .:? "team" <*> o .:? "name" <*> o .:? "normalized" @@ -126,7 +127,6 @@ instance FromJSON UserDoc where <*> o .:? "sso" <*> o .:? "email_unvalidated" <*> o .:? "searchable" - <*> o .:? "type" searchVisibilityInboundFieldName :: Key searchVisibilityInboundFieldName = "search_visibility_inbound" @@ -145,12 +145,10 @@ userDocToContact contactQualifiedId getName userDoc = contactHandle = fromHandle <$> userDoc.udHandle, contactTeam = userDoc.udTeam, contactType = - -- NB: if you have index entries for bots that haven't - -- migrated yet, they will identify as regular users in - -- the search result. this is an accepted limitation. as - -- long as we have cassandra and elastic search involved, - -- the only way around it would be looking up serverIds in - -- cassandra for every query. + -- NB: after wire release upgrade and before ES reindexing, + -- apps may identify as regular users in the search result. + -- this is an accepted limitation and will be fixed in + -- https://github.com/wireapp/wire-server/pull/4947 fromMaybe UserTypeRegular userDoc.udType } diff --git a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs index 7ac88be08f..0dd7eaf06d 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs @@ -150,11 +150,12 @@ indexUserToVersion role IndexUser {..} = ] indexUserToDoc :: SearchVisibilityInbound -> UserType -> Maybe Role -> IndexUser -> UserDoc -indexUserToDoc searchVisInbound type_ mRole IndexUser {..} = +indexUserToDoc searchVisInbound userType mRole IndexUser {..} = if shouldIndex then UserDoc - { udType = Just type_, + { udId = userId, + udType = Just userType, udSearchable = value <$> searchable, udEmailUnvalidated = value <$> unverifiedEmail, udSso = sso . value =<< ssoId, @@ -170,8 +171,7 @@ indexUserToDoc searchVisInbound type_ mRole IndexUser {..} = udHandle = value <$> handle, udNormalized = Just $ normalized name.value.fromName, udName = Just name.value, - udTeam = value <$> teamId, - udId = userId + udTeam = value <$> teamId } else -- We insert a tombstone-style user here, as it's easier than -- deleting the old one. It's mostly empty, but having the status here From 2a8370d3a97d6c662c6e34ffdaf3efbaae2a5939 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 12:50:24 +0100 Subject: [PATCH 26/29] Fix source comment --- integration/test/Test/Apps.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/test/Test/Apps.hs b/integration/test/Test/Apps.hs index 33ffda8f03..14991a5375 100644 --- a/integration/test/Test/Apps.hs +++ b/integration/test/Test/Apps.hs @@ -98,7 +98,7 @@ testCreateApp = do BrigI.refreshIndex domain foundUserType new.name "app" - -- Owner and regular member still have the type "regular" + -- Regular members still have the type "regular" memberName <- regularMember %. "name" & asString foundUserType memberName "regular" From 26e041010dff6e9968d8eddf168ee64eebe768e7 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 15:02:52 +0100 Subject: [PATCH 27/29] rm stale changelog entry. (don't know how the "add get-app endpoint got here, but it doesn't belong.) --- changelog.d/1-api-changes/add-get-app-endpoint | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 changelog.d/1-api-changes/add-get-app-endpoint diff --git a/changelog.d/1-api-changes/add-get-app-endpoint b/changelog.d/1-api-changes/add-get-app-endpoint deleted file mode 100644 index f486179017..0000000000 --- a/changelog.d/1-api-changes/add-get-app-endpoint +++ /dev/null @@ -1,8 +0,0 @@ -Add "get app" endpoint to Brig (`GET /teams/:tid/apps/:id`) - -Elastic Search re-indexing requires postgres access now. - -You need to do a full re-index of ElasticSearch immediately after the -upgrade. Between server upgrade and a full re-index, old-style bots -(spawned by services) may identify as be listed as regular users in -search results. From d7189b08ac54ee1d0abcaf8f586443d43fdd2eb4 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 15:23:25 +0100 Subject: [PATCH 28/29] Fix: allow user doc to not contain user type field. --- .../src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs | 2 +- libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs | 6 +++--- libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs | 2 +- .../test/unit/Wire/UserSubsystem/InterpreterSpec.hs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs index 54aa772dd8..e5585735bd 100644 --- a/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs +++ b/libs/wire-subsystems/src/Wire/IndexedUserStore/Bulk/ElasticSearch.hs @@ -129,7 +129,7 @@ syncAllUsersWithVersion mkVersion = mkUserDoc indexUser = indexUserToDoc (vis indexUser) - (fromMaybe (error "impossible") (Map.lookup indexUser.userId userTypes)) + (Map.lookup indexUser.userId userTypes) ((.value) <$> Map.lookup indexUser.userId roles) indexUser mkDocVersion u = mkVersion . ES.ExternalDocVersion . docVersion $ indexUserToVersion (Map.lookup u.userId roles) u diff --git a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs index 0dd7eaf06d..824fe49e24 100644 --- a/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs +++ b/libs/wire-subsystems/src/Wire/UserStore/IndexUser.hs @@ -149,13 +149,13 @@ indexUserToVersion role IndexUser {..} = const () <$$> writeTimeBumper ] -indexUserToDoc :: SearchVisibilityInbound -> UserType -> Maybe Role -> IndexUser -> UserDoc -indexUserToDoc searchVisInbound userType mRole IndexUser {..} = +indexUserToDoc :: SearchVisibilityInbound -> Maybe UserType -> Maybe Role -> IndexUser -> UserDoc +indexUserToDoc searchVisInbound mUserType mRole IndexUser {..} = if shouldIndex then UserDoc { udId = userId, - udType = Just userType, + udType = mUserType, udSearchable = value <$> searchable, udEmailUnvalidated = value <$> unverifiedEmail, udSso = sso . value =<< ssoId, diff --git a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs index 5f7ba9c70f..c6524d8e6b 100644 --- a/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs @@ -734,7 +734,7 @@ syncUserIndex uid = tm <- maybe (pure Nothing) (selectTeamMember . value) indexUser.teamId userType <- getUserType indexUser.userId (indexUser.teamId <&> (.value)) (indexUser.serviceId <&> (.value)) let mRole = tm >>= mkRoleWithWriteTime - userDoc = indexUserToDoc vis userType (value <$> mRole) indexUser + userDoc = indexUserToDoc vis (Just userType) (value <$> mRole) indexUser version = ES.ExternalGT . ES.ExternalDocVersion . docVersion $ indexUserToVersion mRole indexUser Metrics.incCounter indexUpdateCounter IndexedUserStore.upsert (userIdToDocId uid) userDoc version diff --git a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs index 2965f37203..3c93c88d4d 100644 --- a/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/UserSubsystem/InterpreterSpec.hs @@ -1103,7 +1103,7 @@ spec = describe "UserSubsystem.Interpreter" do storedUserToDoc user = let indexUser = storedUserToIndexUser user userType = if isJust user.serviceId then UserTypeBot else UserTypeRegular - in indexUserToDoc defaultSearchVisibilityInbound userType Nothing indexUser + in indexUserToDoc defaultSearchVisibilityInbound (Just userType) Nothing indexUser indexFromStoredUsers :: [StoredUser] -> UserIndex indexFromStoredUsers storedUsers = do From ce9e8c67dc2edf6e367951dfb4265f6f2763152c Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 13 Jan 2026 15:27:19 +0100 Subject: [PATCH 29/29] Add important changelog info. --- changelog.d/0-release-notes/add-type-field-to-contact | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/0-release-notes/add-type-field-to-contact diff --git a/changelog.d/0-release-notes/add-type-field-to-contact b/changelog.d/0-release-notes/add-type-field-to-contact new file mode 100644 index 0000000000..10d7af72c0 --- /dev/null +++ b/changelog.d/0-release-notes/add-type-field-to-contact @@ -0,0 +1,3 @@ +Since the index mapping has been updated, the elastic search index +needs to be refilled from Cassandra, see +https://docs.wire.com/latest/developer/reference/elastic-search.html?h=index#refill-es-documents-from-cassandra \ No newline at end of file