feat: introduce and handle `checkPermissionsToJoinCommunity()`

This is an improved version to check wether a user has permission to
join a community and updating the join community view accordingly.

We now asynchronously do all the checks in status-go and process
a single result upon token permission updates, additions and deletions.

Depends on: https://github.com/status-im/status-go/pull/3494

Closes #10481 #4939
This commit is contained in:
Pascal Precht 2023-06-06 20:11:22 +02:00 committed by Follow the white rabbit
parent 3d6a5b1785
commit fe491aba6e
9 changed files with 166 additions and 48 deletions

View File

@ -257,6 +257,7 @@ proc init*(self: Controller) =
let args = CommunityTokenPermissionArgs(e)
if (args.communityId == self.sectionId):
self.delegate.onCommunityTokenPermissionCreated(args.communityId, args.tokenPermission)
self.communityService.asyncCheckPermissionsToJoin(self.sectionId)
self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED) do(e: Args):
let args = CommunityTokenPermissionArgs(e)
@ -267,6 +268,8 @@ proc init*(self: Controller) =
let args = CommunityTokenPermissionArgs(e)
if (args.communityId == self.sectionId):
self.delegate.onCommunityTokenPermissionUpdated(args.communityId, args.tokenPermission)
self.communityService.asyncCheckPermissionsToJoin(self.sectionId)
self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED) do(e: Args):
let args = CommunityTokenPermissionArgs(e)
@ -277,12 +280,18 @@ proc init*(self: Controller) =
let args = CommunityTokenPermissionRemovedArgs(e)
if (args.communityId == self.sectionId):
self.delegate.onCommunityTokenPermissionDeleted(args.communityId, args.permissionId)
self.communityService.asyncCheckPermissionsToJoin(self.sectionId)
self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED) do(e: Args):
let args = CommunityTokenPermissionArgs(e)
if (args.communityId == self.sectionId):
self.delegate.onCommunityTokenPermissionDeletionFailed(args.communityId)
self.events.on(SIGNAL_CHECK_PERMISSIONS_TO_JOIN_RESPONSE) do(e: Args):
let args = CheckPermissionsToJoinResponseArgs(e)
if (args.communityId == self.sectionId):
self.delegate.onCommunityCheckPermissionsToJoinResponse(args.checkPermissionsToJoinResponse)
self.events.on(SIGNAL_COMMUNITY_TOKEN_METADATA_ADDED) do(e: Args):
let args = CommunityTokenMetadataArgs(e)
if (args.communityId == self.sectionId):
@ -660,3 +669,6 @@ proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int,
proc getCommunityTokenList*(self: Controller): seq[CommunityTokenDto] =
return self.communityTokensService.getCommunityTokens(self.getMySectionId())
proc asyncCheckPermissionsToJoin*(self: Controller) =
self.communityService.asyncCheckPermissionsToJoin(self.getMySectionId())

View File

@ -396,3 +396,6 @@ method requestToJoinCommunityWithAuthentication*(self: AccessInterface, communit
method onOwnedcollectiblesUpdated*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onCommunityCheckPermissionsToJoinResponse*(self: AccessInterface, checkPermissionsToJoinResponse: CheckPermissionsToJoinResponseDto) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -280,23 +280,14 @@ proc rebuildCommunityTokenPermissionsModel(self: Module) =
let community = self.controller.getMyCommunity()
var tokenPermissionsItems: seq[TokenPermissionItem] = @[]
var allTokenRequirementsMet = false
for id, tokenPermission in community.tokenPermissions:
# TODO: for startes we only deal with "become member" permissions
if tokenPermission.`type` == TokenPermissionType.BecomeMember:
let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission)
# multiple permissions of the same type act as logical OR
# so if at least one of them is fulfilled we can mark the view
# as all lights green
if tokenPermissionItem.tokenCriteriaMet:
allTokenRequirementsMet = true
tokenPermissionsItems.add(tokenPermissionItem)
self.view.tokenPermissionsModel().setItems(tokenPermissionsItems)
self.view.setAllTokenRequirementsMet(allTokenRequirementsMet)
self.view.setRequiresTokenPermissionToJoin(tokenPermissionsItems.len > 0)
proc initCommunityTokenPermissionsModel(self: Module) =
@ -397,6 +388,7 @@ method onChatsLoaded*(
let community = self.controller.getMyCommunity()
self.view.setAmIMember(community.joined)
self.initCommunityTokenPermissionsModel()
self.controller.asyncCheckPermissionsToJoin()
let activeChatId = self.controller.getActiveChatId()
let isCurrentSectionActive = self.controller.getIsCurrentSectionActive()
@ -792,33 +784,54 @@ method onCommunityTokenPermissionCreated*(self: Module, communityId: string, tok
if tokenPermission.`type` == TokenPermissionType.BecomeMember:
let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission)
if tokenPermissionItem.tokenCriteriaMet:
self.view.setAllTokenRequirementsMet(true)
self.view.tokenPermissionsModel.addItem(tokenPermissionItem)
self.view.setRequiresTokenPermissionToJoin(true)
singletonInstance.globalEvents.showCommunityTokenPermissionCreatedNotification(communityId, "Community permission created", "A token permission has been added")
method onCommunityCheckPermissionsToJoinResponse*(self: Module, checkPermissionsToJoinResponse: CheckPermissionsToJoinResponseDto) =
let community = self.controller.getMyCommunity()
self.view.setAllTokenRequirementsMet(checkPermissionsToJoinResponse.satisfied)
for id, criteriaResult in checkPermissionsToJoinResponse.permissions:
if community.tokenPermissions.hasKey(id):
let tokenPermissionItem = self.view.tokenPermissionsModel.getItemById(id)
var updatedTokenCriteriaItems: seq[TokenCriteriaItem] = @[]
var permissionSatisfied = true
for index, tokenCriteriaItem in tokenPermissionItem.getTokenCriteria().getItems():
let updatedTokenCriteriaItem = initTokenCriteriaItem(
tokenCriteriaItem.symbol,
tokenCriteriaItem.name,
tokenCriteriaItem.amount,
tokenCriteriaItem.`type`,
tokenCriteriaItem.ensPattern,
criteriaResult.criteria[index]
)
if criteriaResult.criteria[index] == false:
permissionSatisfied = false
updatedTokenCriteriaItems.add(updatedTokenCriteriaItem)
let updatedTokenPermissionItem = initTokenPermissionItem(
tokenPermissionItem.id,
tokenPermissionItem.`type`,
updatedTokenCriteriaItems,
@[], # TODO: handle chat list items
tokenPermissionItem.isPrivate,
permissionSatisfied
)
self.view.tokenPermissionsModel.updateItem(id, updatedTokenPermissionItem)
method onCommunityTokenPermissionUpdated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) =
if tokenPermission.`type` == TokenPermissionType.BecomeMember:
let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission)
self.view.tokenPermissionsModel.updateItem(tokenPermission.id, tokenPermissionItem)
if tokenPermissionItem.tokenCriteriaMet:
self.view.setAllTokenRequirementsMet(true)
return
# we now need to check whether any other permission criteria where met.
let community = self.controller.getMyCommunity()
for id, permission in community.tokenPermissions:
if id != tokenPermission.id:
for tc in permission.tokenCriteria:
let balance = self.controller.allAccountsTokenBalance(tc.symbol)
let amount = tc.amount.parseFloat
let tokenCriteriaMet = balance >= amount
if tokenCriteriaMet:
return
self.view.setAllTokenRequirementsMet(false)
singletonInstance.globalEvents.showCommunityTokenPermissionUpdatedNotification(communityId, "Community permission updated", "A token permission has been updated")
method onCommunityTokenPermissionCreationFailed*(self: Module, communityId: string) =
@ -1283,33 +1296,17 @@ method requestToJoinCommunityWithAuthentication*(self: Module, communityId: stri
proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem =
var tokenCriteriaItems: seq[TokenCriteriaItem] = @[]
var allTokenCriteriaMet = true
for tc in tokenPermission.tokenCriteria:
var tokenCriteriaMet = false
let amount = tc.amount.parseFloat
if tc.`type` == TokenType.ERC20:
let balance = self.controller.allAccountsTokenBalance(tc.symbol)
tokenCriteriaMet = balance >= amount
if tc.`type` == TokenType.ERC721:
for chainId, address in tc.contractAddresses:
tokenCriteriaMet = self.controller.ownsCollectible(chainId, address, tc.tokenIds)
if tokenCriteriaMet:
break
let tokenCriteriaItem = initTokenCriteriaItem(
tc.symbol,
tc.name,
amount,
tc.amount.parseFloat,
tc.`type`.int,
tc.ensPattern,
tokenCriteriaMet
false # tokenCriteriaMet will be updated by a call to checkPermissionsToJoin
)
if not tokenCriteriaMet:
allTokenCriteriaMet = false
tokenCriteriaItems.add(tokenCriteriaItem)
@ -1319,7 +1316,7 @@ proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPerm
tokenCriteriaItems,
@[], # TODO: handle chat list items
tokenPermission.isPrivate,
allTokenCriteriaMet
false # allTokenCriteriaMet will be update by a call to checkPermissinosToJoin
)
return tokenPermissionItem

View File

@ -103,6 +103,13 @@ QtObject:
self.endRemoveRows()
self.countChanged()
proc getItemById*(self: TokenPermissionsModel, permissionId: string): TokenPermissionItem =
let idx = self.findIndexById(permissionId)
if(idx == -1):
return
return self.items[idx]
proc updateItem*(self: TokenPermissionsModel, permissionId: string, item: TokenPermissionItem) =
let idx = self.findIndexById(permissionId)
if(idx == -1):

View File

@ -101,4 +101,23 @@ const asyncRequestToJoinCommunityTask: Task = proc(argEncoded: string) {.gcsafe,
"communityId": arg.communityId,
"ensName": arg.ensName,
"password": arg.password
})
})
type
AsyncCheckPermissionsToJoinTaskArg = ref object of QObjectTaskArg
communityId: string
const asyncCheckPermissionsToJoinTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncCheckPermissionsToJoinTaskArg](argEncoded)
try:
let response = status_go.checkPermissionsToJoinCommunity(arg.communityId)
arg.finish(%* {
"response": response,
"communityId": arg.communityId,
"error": "",
})
except Exception as e:
arg.finish(%* {
"communityId": arg.communityId,
"error": e.msg,
})

View File

@ -136,6 +136,18 @@ type DiscordImportTaskProgress* = object
stopped*: bool
state*: string
type AccountChainIDsCombinationDto* = object
address*: string
chainIds*: seq[int]
type CheckPermissionToJoinResultDto* = object
criteria*: seq[bool]
type CheckPermissionsToJoinResponseDto* = object
satisfied*: bool
permissions*: Table[string, CheckPermissionToJoinResultDto]
validCombinations*: seq[AccountChainIDsCombinationDto]
proc toCommunityAdminSettingsDto*(jsonObj: JsonNode): CommunityAdminSettingsDto =
result = CommunityAdminSettingsDto()
discard jsonObj.getProp("pinMessageAllMembersEnabled", result.pinMessageAllMembersEnabled)
@ -246,6 +258,36 @@ proc toCommunityTokenPermissionDto*(jsonObj: JsonNode): CommunityTokenPermission
if jsonObj.hasKey("key"):
discard jsonObj.getProp("key", result.id)
proc toCheckPermissionToJoinResultDto*(jsonObj: JsonNode): CheckPermissionToJoinResultDto =
result = CheckPermissionToJoinResultDto()
var criteriaObj: JsonNode
if(jsonObj.getProp("criteria", criteriaObj) and criteriaObj.kind == JArray):
for c in criteriaObj:
result.criteria.add(c.getBool)
proc toAccountChainIDsCombinationDto*(jsonObj: JsonNode): AccountChainIDsCombinationDto =
result = AccountChainIDsCombinationDto()
discard jsonObj.getProp("address", result.address)
var chainIdsObj: JsonNode
if(jsonObj.getProp("chainIds", chainIdsObj) and chainIdsObj.kind == JArray):
for chainId in chainIdsObj:
result.chainIds.add(chainId.getInt)
proc toCheckPermissionsToJoinResponseDto*(jsonObj: JsonNode): CheckPermissionsToJoinResponseDto =
result = CheckPermissionsToJoinResponseDto()
discard jsonObj.getProp("satisfied", result.satisfied)
var validCombinationsObj: JsonNode
if(jsonObj.getProp("validCombinations", validCombinationsObj) and validCombinationsObj.kind == JArray):
for validCombination in validCombinationsObj:
result.validCombinations.add(validCombination.toAccountChainIDsCombinationDto)
var permissionsObj: JsonNode
if(jsonObj.getProp("permissions", permissionsObj) and permissionsObj.kind == JObject):
result.permissions = initTable[string, CheckPermissionToJoinResultDto]()
for permissionId, permission in permissionsObj:
result.permissions[permissionId] = permission.toCheckPermissionToJoinResultDto
proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
result = CommunityDto()
discard jsonObj.getProp("id", result.id)

View File

@ -111,6 +111,10 @@ type
totalChunksCount*: int
currentChunk*: int
CheckPermissionsToJoinResponseArgs* = ref object of Args
communityId*: string
checkPermissionsToJoinResponse*: CheckPermissionsToJoinResponseDto
# Signals which may be emitted by this service:
const SIGNAL_COMMUNITY_DATA_LOADED* = "communityDataLoaded"
const SIGNAL_COMMUNITY_JOINED* = "communityJoined"
@ -172,6 +176,8 @@ const SIGNAL_COMMUNITY_INFO_ALREADY_REQUESTED* = "communityInfoAlreadyRequested"
const TOKEN_PERMISSIONS_ADDED = "tokenPermissionsAdded"
const TOKEN_PERMISSIONS_MODIFIED = "tokenPermissionsModified"
const SIGNAL_CHECK_PERMISSIONS_TO_JOIN_RESPONSE* = "checkPermissionsToJoinResponse"
QtObject:
type
Service* = ref object of QObject
@ -1362,6 +1368,33 @@ QtObject:
self.events.emit(SIGNAL_COMMUNITY_DATA_IMPORTED, CommunityArgs(community: community))
proc asyncCheckPermissionsToJoin*(self: Service, communityId: string) =
try:
let arg = AsyncCheckPermissionsToJoinTaskArg(
tptr: cast[ByteAddress](asyncCheckPermissionsToJoinTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncCheckPermissionsToJoinDone",
communityId: communityId
)
self.threadpool.start(arg)
except Exception as e:
error "Error checking permissions to join community", msg = e.msg
proc onAsyncCheckPermissionsToJoinDone*(self: Service, rpcResponse: string) {.slot.} =
try:
let rpcResponseObj = rpcResponse.parseJson
if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "":
let error = Json.decode($rpcResponseObj["error"], RpcError)
error "Error requesting community info", msg = error.message
return
let communityId = rpcResponseObj{"communityId"}.getStr()
let checkPermissionsToJoinResponse = rpcResponseObj["response"]["result"].toCheckPermissionsToJoinResponseDto
self.events.emit(SIGNAL_CHECK_PERMISSIONS_TO_JOIN_RESPONSE, CheckPermissionsToJoinResponseArgs(communityId: communityId, checkPermissionsToJoinResponse: checkPermissionsToJoinResponse))
except Exception as e:
let errMsg = e.msg
error "error checking permissions to join: ", errMsg
proc asyncRequestToJoinCommunity*(self: Service, communityId: string, ensName: string, password: string) =
try:
let arg = AsyncRequestToJoinCommunityTaskArg(

View File

@ -39,6 +39,11 @@ proc requestToJoinCommunity*(communityId: string, ensName: string, password: str
"password": if passwordToSend != "": utils.hashPassword(password) else: ""
}])
proc checkPermissionsToJoinCommunity*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("checkPermissionsToJoinCommunity".prefix, %*[{
"communityId": communityId
}])
proc myPendingRequestsToJoin*(): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("myPendingRequestsToJoin".prefix)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 87ce6cfbcd8c6dd9ce3f7dee8b2a3f7b43738e81
Subproject commit 1a2ca21070456e153aac27b8a14afa14006652ef