Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
3e7403a
Remove references to non-existent note
eyeinsky Sep 16, 2025
ecb8603
Make the `Wire.API.Team.Member.userId` lens more general
eyeinsky Oct 1, 2025
ec9eeb8
Minor refactor
eyeinsky Sep 19, 2025
9afddae
Add `searchable` field to data types
eyeinsky Sep 16, 2025
4f846ae
Add Elastic Search boolean field type
eyeinsky Sep 25, 2025
991b7d2
Add `POST /users/:uid/searchable`
eyeinsky Sep 19, 2025
1afd1ff
Add Elastic Search indexing
eyeinsky Sep 25, 2025
a4092a3
Filter by searchable in Elastic Search
eyeinsky Sep 25, 2025
0e062d4
Filter by `searchable` in exact handle search
eyeinsky Oct 1, 2025
06c3d05
Test searchable field and contact search
eyeinsky Sep 19, 2025
a83fca9
Use common CQL splice for team member queries
eyeinsky Oct 3, 2025
bc91e9f
Test /team/:tid/members?searchable=false
eyeinsky Oct 2, 2025
d7a4d92
[wip] Filter `searchable` with `/team/:tid/members?searchable=false`
eyeinsky Oct 3, 2025
845fe82
Revert "[wip] Filter `searchable` with `/team/:tid/members?searchable…
eyeinsky Oct 6, 2025
08e2a73
Add query param to Brig
eyeinsky Oct 7, 2025
59eebad
Update services/brig/src/Brig/Provider/API.hs
eyeinsky Oct 8, 2025
32afbe1
Update libs/wire-subsystems/src/Wire/UserSubsystem/Interpreter.hs
eyeinsky Oct 8, 2025
34f9868
fixup! Add `POST /users/:uid/searchable`
eyeinsky Oct 8, 2025
5b8636a
fixup! Test searchable field and contact search
eyeinsky Oct 8, 2025
8393d5c
fixup! Add `POST /users/:uid/searchable`
eyeinsky Oct 8, 2025
d48d773
Move test from brig to integration package
eyeinsky Oct 8, 2025
91a64a4
fixup! Add query param to Brig
eyeinsky Oct 9, 2025
f4304ee
Partially revert "Minor refactor": inline getProfile back again
eyeinsky Oct 9, 2025
3e6cc0c
Minor refactor: use record syntax, deduplicate golden tests
eyeinsky Oct 9, 2025
367fcf7
`make sanitize-pr`
eyeinsky Oct 9, 2025
07b11aa
fixup! Add query param to Brig
eyeinsky Oct 9, 2025
bceac5b
fixup! Add `POST /users/:uid/searchable`
eyeinsky Oct 9, 2025
3de1b14
fixup! Add query param to Brig
eyeinsky Oct 9, 2025
8fd8b6c
Update libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
eyeinsky Oct 10, 2025
5421654
fixup! Move test from brig to integration package
eyeinsky Oct 10, 2025
ce77a44
fixup! Move test from brig to integration package
eyeinsky Oct 10, 2025
c1d99cd
fixup! Move test from brig to integration package
eyeinsky Oct 10, 2025
6f670bc
fixup! Move test from brig to integration package
eyeinsky Oct 10, 2025
ee2094f
fixup! Move test from brig to integration package
eyeinsky Oct 10, 2025
4567ce9
fixup! Add query param to Brig
eyeinsky Oct 10, 2025
22b12d4
fixup! Minor refactor: use record syntax, deduplicate golden tests
eyeinsky Oct 10, 2025
419a017
fixup! Add query param to Brig
eyeinsky Oct 10, 2025
e63e93b
Make /teams/:tid/search and /teams/:tid/search?searchable=true equal
eyeinsky Oct 10, 2025
7716271
Create all test users' presence in /teams/:tid/search
eyeinsky Oct 10, 2025
c9cd3d6
Add changelog entry
eyeinsky Oct 10, 2025
0ade5c3
fixup! Add Elastic Search indexing
eyeinsky Oct 13, 2025
a45b069
fixup! Add `searchable` field to data types
eyeinsky Oct 13, 2025
23da170
fixup! Move test from brig to integration package
eyeinsky Oct 13, 2025
c5667e6
Update integration/test/Test/Search.hs
eyeinsky Oct 13, 2025
40e049a
fixup! Move test from brig to integration package
eyeinsky Oct 13, 2025
6c5b0d0
fixup! Move test from brig to integration package
eyeinsky Oct 13, 2025
680d092
Move removeTeamCollaborator to API.Galley
eyeinsky Oct 13, 2025
2846097
make sanitize-pr
eyeinsky Oct 13, 2025
5c8794a
fixup! Add `POST /users/:uid/searchable`
eyeinsky Oct 13, 2025
80ad6b6
Wrap searchable POST body to JSON object
eyeinsky Oct 13, 2025
791a4cb
Pass explicit field value down to ES; adapt test
eyeinsky Oct 14, 2025
5acb9f7
Revert "Pass explicit field value down to ES; adapt test"
eyeinsky Oct 14, 2025
1233dc0
make sanitize-pr
eyeinsky Oct 15, 2025
8de6015
fix templates
eyeinsky Oct 15, 2025
cfd7a91
add seacrhable to golden tests
eyeinsky Oct 15, 2025
8e395fa
Implement searchable flag to MockInterpreters
eyeinsky Oct 15, 2025
7195d36
bump CI
eyeinsky Oct 15, 2025
28447bc
fixup! bump CI
eyeinsky Oct 16, 2025
25fa40f
second attempt at correct /team/:tid/search
eyeinsky Oct 16, 2025
18cc561
fixup! second attempt at correct /team/:tid/search
eyeinsky Oct 16, 2025
6982181
Use Snake case object field for SetSearchable
eyeinsky Oct 16, 2025
bd04a10
fixup! second attempt at correct /team/:tid/search
eyeinsky Oct 16, 2025
0ea4a7c
fixup! Use Snake case object field for SetSearchable
eyeinsky Oct 17, 2025
8d46700
Add test to check that legacy users are found
eyeinsky Oct 17, 2025
8a74d63
fixup! Add test to check that legacy users are found
eyeinsky Oct 17, 2025
7879f1c
fixup! Add test to check that legacy users are found
eyeinsky Oct 17, 2025
cfc9caf
fixup! Add test to check that legacy users are found
eyeinsky Oct 17, 2025
fe13075
make sanitize-pr
eyeinsky Oct 17, 2025
1646b92
fixup! make sanitize-pr
eyeinsky Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/2-features/WPB-20214
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add `searchable` field to users, users who have it set to `false` won't be found by the public endpoint.
Add `POST /users/:uid/searchable` endpoint where team admin can change it for user.
Add `/teams/:tid/search?searchable=false`, where the query parameter makes it return only non-searchable users.
21 changes: 16 additions & 5 deletions integration/test/API/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ searchTeamWithSearchTerm user q = searchTeam user [("q", q)]
searchTeamAll :: (HasCallStack, MakesValue user) => user -> App Response
searchTeamAll user = searchTeam user [("q", ""), ("size", "100"), ("sortby", "created_at"), ("sortorder", "desc")]

setUserSearchable :: (MakesValue user) => user -> String -> Bool -> App Response
setUserSearchable self uid searchable = do
req <- baseRequest self Brig Versioned $ joinHttpPath ["users", uid, "searchable"]
submit "POST" $ addJSONObject ["set_searchable" .= searchable] req

getAPIVersion :: (HasCallStack, MakesValue domain) => domain -> App Response
getAPIVersion domain = do
req <- baseRequest domain Brig Unversioned $ "/api-version"
Expand Down Expand Up @@ -1207,8 +1212,14 @@ refreshAppCookie u tid appId = do
req <- baseRequest u Brig Versioned $ joinHttpPath ["teams", tid, "apps", appId, "cookies"]
submit "POST" req

removeTeamCollaborator :: (MakesValue owner, MakesValue collaborator, HasCallStack) => owner -> String -> collaborator -> App Response
removeTeamCollaborator owner tid collaborator = do
(_, collabId) <- objQid collaborator
req <- baseRequest owner Galley Versioned $ joinHttpPath ["teams", tid, "collaborators", collabId]
submit "DELETE" req
-- | https://staging-nginz-https.zinfra.io/v12/api/swagger-ui/#/default/check-user-handle
checkHandle :: (MakesValue user) => user -> String -> App Response
checkHandle self handle = do
req <- baseRequest self Brig Versioned (joinHttpPath ["handles", handle])
submit "HEAD" req

-- | https://staging-nginz-https.zinfra.io/v12/api/swagger-ui/#/default/check-user-handles
checkHandles :: (MakesValue user) => user -> [String] -> App Response
checkHandles self handles = do
req <- baseRequest self Brig Versioned (joinHttpPath ["handles"]) <&> addJSONObject ["handles" .= handles]
submit "POST" req
6 changes: 6 additions & 0 deletions integration/test/API/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -867,3 +867,9 @@ resetConversation user groupId epoch = do
req <- baseRequest user Galley Versioned (joinHttpPath ["mls", "reset-conversation"])
let payload = object ["group_id" .= groupId, "epoch" .= epoch]
submit "POST" $ req & addJSON payload

removeTeamCollaborator :: (MakesValue owner, MakesValue collaborator, HasCallStack) => owner -> String -> collaborator -> App Response
removeTeamCollaborator owner tid collaborator = do
(_, collabId) <- objQid collaborator
req <- baseRequest owner Galley Versioned $ joinHttpPath ["teams", tid, "collaborators", collabId]
submit "DELETE" req
122 changes: 122 additions & 0 deletions integration/test/Test/Search.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import qualified API.Common as API
import API.Galley
import qualified API.Galley as Galley
import qualified API.GalleyInternal as GalleyI
import qualified Data.Set as Set
import GHC.Stack
import SetupHelpers
import Testlib.Assertions
Expand Down Expand Up @@ -358,3 +359,124 @@ testTeamSearchUserIncludesUserGroups = do
let expectedUgs = fromMaybe [] (lookup uid expected)
actualUgs <- for ugs asString
actualUgs `shouldMatchSet` expectedUgs

testUserSearchable :: App ()
testUserSearchable = do
-- Create team and all users who are part of this test
(owner, tid, [u1, u2, u3]) <- createTeam OwnDomain 4
admin <- createTeamMember owner def {role = "admin"}
let everyone = [owner, u1, admin, u2, u3]
everyone'sUidSet <- Set.fromList <$> mapM objId everyone

-- All users are searchable by default
assertBool "created users are searchable by default" . and =<< mapM (\u -> u %. "searchable" & asBool) everyone

-- Setting self to non-searchable won't work -- only admin can do it.
u1id <- u1 %. "id" & asString
BrigP.setUserSearchable u1 u1id False `bindResponse` \resp -> do
resp.status `shouldMatchInt` 403
resp.json %. "label" `shouldMatch` "insufficient-permissions"

BrigP.getUser u1 u1 `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
(resp.json %. "searchable") `shouldMatch` True

-- Team admin can set user to non-searchable.
BrigP.setUserSearchable admin u1id False `bindResponse` \resp -> resp.status `shouldMatchInt` 200
BrigP.getUser u1 u1 `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
(resp.json %. "searchable") `shouldMatch` False

-- Team owner can, too.
BrigP.setUserSearchable owner u1id True `bindResponse` \resp -> resp.status `shouldMatchInt` 200
BrigP.setUserSearchable owner u1id False `bindResponse` \resp -> resp.status `shouldMatchInt` 200

-- By default created team members are found.
u2id <- u2 %. "id" & asString
BrigI.refreshIndex OwnDomain
withFoundDocs u1 (u2 %. "name") $ \docs -> do
foundUids <- for docs objId
assertBool "u1 must find u2 as they are searchable by default" $ u2id `elem` foundUids

-- User set to non-searchable is not found by other team members.
u3id <- u3 %. "id" & asString
BrigP.setUserSearchable owner u3id False `bindResponse` \resp -> resp.status `shouldMatchInt` 200
BrigI.refreshIndex OwnDomain
withFoundDocs u1 (u3 %. "name") $ \docs -> do
foundUids <- for docs objId
assertBool "u1 must not find u3 as they are set non-searchable" $ notElem u3id foundUids

-- Even admin nor owner won't find non-searchable users via /search/contacts
withFoundDocs admin (u3 %. "name") $ \docs -> do
foundUids <- for docs objId
assertBool "Team admin won't find non-searchable user" $ notElem u3id foundUids
withFoundDocs owner (u3 %. "name") $ \docs -> do
foundUids <- for docs objId
assertBool "Team owner won't find non-searchable user from /search/contacts" $ notElem u3id foundUids

-- Check for handle being available with HTTP HEAD still shows that the handle used by non-searchable users is not available
u3handle <- API.randomHandle
BrigP.putHandle u3 u3handle `bindResponse` assertSuccess
BrigP.checkHandle u2 u3handle `bindResponse` \resp -> resp.status `shouldMatchInt` 200 -- (200 means "handle is taken", 404 would be "not found")

-- Handle for POST /handles still works for non-searchable users
u2handle <- API.randomHandle
BrigP.putHandle u2 u2handle `bindResponse` assertSuccess
BrigP.checkHandles u1 [u3handle, u2handle] `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
freeHandles <- resp.json & asList
assertBool "POST /handles filters all taken handles, even for regular members" $ null freeHandles

-- Regular user can't find non-searchable team member by exact handle.
withFoundDocs u1 u3handle $ \docs -> do
foundUids <- for docs objId
assertBool "u1 must not find non-searchable u3 by exact handle" $ notElem u3id foundUids

-- /teams/:tid/members gets all members, both searchable and non-searchable
getTeamMembers u1 tid `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
docs <- resp.json %. "members" >>= asList
foundUids <- mapM (\m -> m %. "user" & asString) docs
assertBool "/teams/:tid/members returns all users in team"
$ Set.fromList foundUids
== everyone'sUidSet

-- /teams/:tid/search also returns all users from team
BrigP.searchTeam admin [] `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
docs <- resp.json %. "documents" >>= asList
foundUids <- mapM (\m -> m %. "id" & asString) docs
assertBool "/teams/:tid/search returns all users in team" $ Set.fromList foundUids == everyone'sUidSet

-- /teams/:tid/search?searchable=false gets only non-searchable members
BrigP.searchTeam admin [("searchable", "false")] `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
docs <- resp.json %. "documents" >>= asList
foundUids <- mapM (\m -> m %. "id" & asString) docs
assertBool "/teams/:tid/members?searchable=false returns only non-searchable members"
$ Set.fromList foundUids
== Set.fromList [u1id, u3id]

-- /teams/:tid/search?searchable=true gets only searchable users
BrigP.searchTeam admin [("searchable", "true")] `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
docs <- resp.json %. "documents" >>= asList
foundUids <- mapM (\m -> m %. "id" & asString) docs
ownerUid <- owner %. "id" & asString
adminUid <- admin %. "id" & asString
assertBool "/teams/:tid/search?searchable=true gets only searchable users"
$ Set.fromList foundUids
== Set.fromList [ownerUid, adminUid, u2id]
where
-- Convenience wrapper around search contacts which applies `f` directly to document list.
withFoundDocs ::
(MakesValue user, MakesValue searchTerm) =>
user ->
searchTerm ->
([Value] -> App a) ->
App a
withFoundDocs self term f = do
BrigP.searchContacts self term OwnDomain `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
docs <- resp.json %. "documents" >>= asList
f docs
2 changes: 1 addition & 1 deletion libs/bilge/src/Bilge/Assert.hs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ io <!! aa = do
m (Response (Maybe Lazy.ByteString)) ->
Assertions () ->
m ()
(!!!) io = void . (<!!) io
io !!! aa = void (io <!! aa)
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change from pattern matching to direct function composition appears to be unrelated to the searchability feature. Consider moving this refactoring to a separate commit or PR to maintain clear separation of concerns.

Suggested change
io !!! aa = void (io <!! aa)
m !!! a = do
_ <- m <!! a
pure ()

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of a functionally no-op refactor commit. The single line version is shorter, but still clear.


infix 4 ===

Expand Down
83 changes: 47 additions & 36 deletions libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ instance AsUnion DeleteSelfResponses (Maybe Timeout) where
type ConnectionUpdateResponses = UpdateResponses "Connection unchanged" "Connection updated" UserConnection

type UserAPI =
-- See Note [ephemeral user sideeffect]
Named
Comment on lines 163 to 164
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of 'See Note [ephemeral user sideeffect]' comments appears unrelated to the searchability feature. These documentation changes should be part of a separate commit focused on documentation cleanup.

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These removals are in a separate commit.

"get-user-unqualified"
( Summary "Get a user by UserId"
Expand All @@ -171,16 +170,14 @@ type UserAPI =
:> CaptureUserId "uid"
:> GetUserVerb
)
:<|>
-- See Note [ephemeral user sideeffect]
Named
"get-user-qualified"
( Summary "Get a user by Domain and UserId"
:> ZLocalUser
:> "users"
:> QualifiedCaptureUserId "uid"
:> GetUserVerb
)
:<|> Named
"get-user-qualified"
( Summary "Get a user by Domain and UserId"
:> ZLocalUser
:> "users"
:> QualifiedCaptureUserId "uid"
:> GetUserVerb
)
:<|> Named
"update-user-email"
( Summary "Resend email address validation email."
Expand Down Expand Up @@ -224,19 +221,17 @@ type UserAPI =
]
(Maybe UserProfile)
)
:<|>
-- See Note [ephemeral user sideeffect]
Named
"list-users-by-unqualified-ids-or-handles"
( Summary "List users (deprecated)"
:> Until 'V2
:> Description "The 'ids' and 'handles' parameters are mutually exclusive."
:> ZUser
:> "users"
:> QueryParam' [Optional, Strict, Description "User IDs of users to fetch"] "ids" (CommaSeparatedList UserId)
:> QueryParam' [Optional, Strict, Description "Handles of users to fetch, min 1 and max 4 (the check for handles is rather expensive)"] "handles" (Range 1 4 (CommaSeparatedList Handle))
:> Get '[JSON] [UserProfile]
)
:<|> Named
"list-users-by-unqualified-ids-or-handles"
( Summary "List users (deprecated)"
:> Until 'V2
:> Description "The 'ids' and 'handles' parameters are mutually exclusive."
:> ZUser
:> "users"
:> QueryParam' [Optional, Strict, Description "User IDs of users to fetch"] "ids" (CommaSeparatedList UserId)
:> QueryParam' [Optional, Strict, Description "Handles of users to fetch, min 1 and max 4 (the check for handles is rather expensive)"] "handles" (Range 1 4 (CommaSeparatedList Handle))
:> Get '[JSON] [UserProfile]
)
:<|> Named
"list-users-by-ids-or-handles"
( Summary "List users"
Expand All @@ -247,18 +242,16 @@ type UserAPI =
:> ReqBody '[JSON] ListUsersQuery
:> Post '[JSON] ListUsersById
)
:<|>
-- See Note [ephemeral user sideeffect]
Named
"list-users-by-ids-or-handles@V3"
( Summary "List users"
:> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive."
:> ZUser
:> Until 'V4
:> "list-users"
:> ReqBody '[JSON] ListUsersQuery
:> Post '[JSON] [UserProfile]
)
:<|> Named
"list-users-by-ids-or-handles@V3"
( Summary "List users"
:> Description "The 'qualified_ids' and 'qualified_handles' parameters are mutually exclusive."
:> ZUser
:> Until 'V4
:> "list-users"
:> ReqBody '[JSON] ListUsersQuery
:> Post '[JSON] [UserProfile]
)
:<|> Named
"send-verification-code"
( Summary "Send a verification code to a given email address."
Expand Down Expand Up @@ -294,6 +287,17 @@ type UserAPI =
'[JSON]
(Respond 200 "Protocols supported by the user" (Set BaseProtocolTag))
)
:<|> Named
"set-user-searchable"
( Summary "Set user's visibility in search"
:> From 'V12
:> ZLocalUser
:> "users"
:> CaptureUserId "uid"
:> "searchable"
:> ReqBody '[JSON] SetSearchable
:> Post '[JSON] ()
)

type LastSeenNameDesc = Description "`name` of the last seen user group, used to get the next page when sorting by name."

Expand Down Expand Up @@ -1735,6 +1739,13 @@ type SearchAPI =
]
"email"
EmailVerificationFilter
:> QueryParam'
[ Optional,
Strict,
Description "Optional, return only non-searchable members when false."
]
"searchable"
Bool
:> MultiVerb
'GET
'[JSON]
Expand Down
6 changes: 4 additions & 2 deletions libs/wire-api/src/Wire/API/Team/Member.hs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ data HiddenPerm
| CreateApp
| ManageApps
| RemoveTeamCollaborator
| SetMemberSearchable
deriving (Eq, Ord, Show)

-- | See Note [hidden team roles]
Expand Down Expand Up @@ -570,7 +571,8 @@ roleHiddenPermissions role = HiddenPermissions p p
NewTeamCollaborator,
CreateApp,
ManageApps,
RemoveTeamCollaborator
RemoveTeamCollaborator,
SetMemberSearchable
]
roleHiddenPerms RoleMember =
(roleHiddenPerms RoleExternalPartner <>) $
Expand Down Expand Up @@ -654,7 +656,7 @@ makeLenses ''TeamMemberList'
makeLenses ''NewTeamMember'
makeLenses ''TeamMemberDeleteData

userId :: Lens' TeamMember UserId
userId :: Lens' (TeamMember' tag) UserId
userId = newTeamMember . nUserId

permissions :: Lens (TeamMember' tag1) (TeamMember' tag2) (PermissionType tag1) (PermissionType tag2)
Expand Down
5 changes: 3 additions & 2 deletions libs/wire-api/src/Wire/API/Team/Permission.hs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ serviceWhitelistPermissions =
-- Perm

-- | Team-level permission. Analog to conversation-level 'Action'.
--
-- If you ever think about adding a new permission flag, read Note
-- [team roles] first.
Comment on lines +131 to +133
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The comment about reading 'Note [team roles]' was moved from after the data type definition to before it, but the new SetMemberSearchable permission was added without any indication that this note was consulted. Consider adding a comment confirming this note was reviewed for the new permission.

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it was moved, it was definitely read. I moved it because it relates to the entire data type (as when it was next to a data constructor, it looked as if it only mattered to that particular one).

data Perm
= CreateConversation
| -- NOTE: This may get overruled by conv level checks in case those are more restrictive
Expand All @@ -153,8 +156,6 @@ data Perm
| DeleteTeam
-- FUTUREWORK: make the verbs in the roles more consistent
-- (CRUD vs. Add,Remove vs; Get,Set vs. Create,Delete etc).
-- If you ever think about adding a new permission flag,
-- read Note [team roles] first.
deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)
deriving (Arbitrary) via (GenericUniform Perm)
deriving (FromJSON, ToJSON) via (CustomEncoded Perm)
Expand Down
Loading