feat: owner and token master permissions (#11942)

This commit is contained in:
Mykhailo Prakhov 2023-08-22 20:09:34 +02:00 committed by GitHub
parent 8941b218d5
commit 3f5df7e3ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 175 additions and 48 deletions

View File

@ -563,11 +563,12 @@ method amIChatAdmin*(self: Module): bool =
let chatDto = self.controller.getChatDetails()
for member in chatDto.members:
if (member.id == singletonInstance.userProfile.getPubKey()):
return member.role == MemberRole.Owner or member.role == MemberRole.Admin
return member.role == MemberRole.Owner or member.role == MemberRole.Admin or member.role == MemberRole.TokenMaster
return false
else:
let communityDto = self.controller.getCommunityDetails()
return communityDto.memberRole == MemberRole.Owner or communityDto.memberRole == MemberRole.Admin
return communityDto.memberRole == MemberRole.Owner or
communityDto.memberRole == MemberRole.Admin or communityDto.memberRole == MemberRole.TokenMaster
method pinMessageAllowedForMembers*(self: Module): bool =
if(self.controller.belongsToCommunity()):
@ -737,7 +738,8 @@ method markMessagesAsRead*(self: Module, messages: seq[string]) =
self.view.model().markAsSeen(messages)
method updateCommunityDetails*(self: Module, community: CommunityDto) =
self.view.setAmIChatAdmin(community.memberRole == MemberRole.Owner or community.memberRole == MemberRole.Admin)
self.view.setAmIChatAdmin(community.memberRole == MemberRole.Owner or
community.memberRole == MemberRole.Admin or community.memberRole == MemberRole.TokenMaster)
self.view.setIsPinMessageAllowedForMembers(community.adminSettings.pinMessageAllMembersEnabled)
proc setChatDetails(self: Module, chatDetails: ChatDto) =

View File

@ -396,11 +396,12 @@ method amIChatAdmin*(self: Module): bool =
let chatDto = self.controller.getChatDetails()
for member in chatDto.members:
if member.id == singletonInstance.userProfile.getPubKey():
return member.role == MemberRole.Owner or member.role == MemberRole.Admin
return member.role == MemberRole.Owner or member.role == MemberRole.Admin or member.role == MemberRole.TokenMaster
return false
else:
let communityDto = self.controller.getCommunityDetails()
return communityDto.memberRole == MemberRole.Owner or communityDto.memberRole == MemberRole.Admin
return communityDto.memberRole == MemberRole.Owner or
communityDto.memberRole == MemberRole.Admin or communityDto.memberRole == MemberRole.TokenMaster
method onUpdateViewOnlyPermissionsSatisfied*(self: Module, value: bool) =
self.view.setViewOnlyPermissionsSatisfied(value)

View File

@ -271,12 +271,6 @@ proc rebuildCommunityTokenPermissionsModel(self: Module) =
let tokenPermissionItem = buildTokenPermissionItem(tokenPermission, chats)
tokenPermissionsItems.add(tokenPermissionItem)
let memberPermissions = filter(tokenPermissionsItems, tokenPermissionsItem =>
tokenPermissionsItem.getType() == TokenPermissionType.BecomeMember.int)
let adminPermissions = filter(tokenPermissionsItems, tokenPermissionsItem =>
tokenPermissionsItem.getType() == TokenPermissionType.BecomeAdmin.int)
self.view.tokenPermissionsModel().setItems(tokenPermissionsItems)
self.reevaluateRequiresTokenPermissionToJoin()

View File

@ -375,6 +375,8 @@ proc init*(self: Controller) =
self.getRemainingSupply(communityToken.chainId, communityToken.address),
self.getRemoteDestructedAmount(communityToken.chainId, communityToken.address))
self.delegate.onRemoteDestructed(communityToken.communityId, communityToken.chainId, communityToken.address, args.remoteDestructAddresses)
if args.status == ContractTransactionStatus.Completed:
self.delegate.onRequestReevaluateMembersPermissionsIfRequired(communityToken.communityId, communityToken.chainId, communityToken.address)
self.events.on(SIGNAL_AIRDROP_STATUS) do(e: Args):
let args = AirdropArgs(e)
@ -383,6 +385,8 @@ proc init*(self: Controller) =
communityToken.address, communityToken.supply,
self.getRemainingSupply(communityToken.chainId, communityToken.address),
self.getRemoteDestructedAmount(communityToken.chainId, communityToken.address))
if args.status == ContractTransactionStatus.Completed:
self.delegate.onRequestReevaluateMembersPermissionsIfRequired(communityToken.communityId, communityToken.chainId, communityToken.address)
self.events.on(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED) do(e: Args):
let args = CommunityTokenOwnersArgs(e)
@ -543,3 +547,6 @@ proc getColorId*(self: Controller, pubkey: string): int =
proc asyncGetRevealedAccountsForAllMembers*(self: Controller, communityId: string) =
self.communityService.asyncGetRevealedAccountsForAllMembers(communityId)
proc asyncReevaluateCommunityMembersPermissions*(self: Controller, communityId: string) =
self.communityService.asyncReevaluateCommunityMembersPermissions(communityId)

View File

@ -315,6 +315,9 @@ method onBurnStateChanged*(self: AccessInterface, communityId: string, chainId:
method onRemoteDestructed*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, addresses: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available")
method onRequestReevaluateMembersPermissionsIfRequired*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onAcceptRequestToJoinFailed*(self: AccessInterface, communityId: string, memberKey: string, requestId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -264,8 +264,7 @@ proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto):
)
# Get community members' revealed accounts
# We will update the model later when we finish loading the accounts
# TODO add TokenMaster here too when it's available
if communityDetails.memberRole == MemberRole.Owner:
if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster:
self.controller.asyncGetRevealedAccountsForAllMembers(channelGroup.id)
let unviewedCount = channelGroup.unviewedMessagesCount
@ -1106,6 +1105,16 @@ method onRemoteDestructed*[T](self: Module[T], communityId: string, chainId: int
if item.id != "":
item.updateRemoteDestructedAddresses(chainId, contractAddress, addresses)
method onRequestReevaluateMembersPermissionsIfRequired*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string) =
let communityDto = self.controller.getCommunityById(communityId)
for _, tokenPermission in communityDto.tokenPermissions.pairs:
for tokenCriteria in tokenPermission.tokenCriteria:
if tokenCriteria.contractAddresses.hasKey(chainId):
let actualAddress = tokenCriteria.contractAddresses[chainId]
if actualAddress == contractAddress:
self.controller.asyncReevaluateCommunityMembersPermissions(communityId)
return
method onAcceptRequestToJoinLoading*[T](self: Module[T], communityId: string, memberKey: string) =
let item = self.view.model().getItemById(communityId)
if item.id != "":

View File

@ -53,6 +53,7 @@ type MemberRole* {.pure} = enum
ManageUsers
ModerateContent
Admin
TokenMaster
type MembershipRequestState* {.pure} = enum
None = 0,

View File

@ -246,6 +246,8 @@ proc toChannelMember*(jsonObj: JsonNode, memberId: string, joined: bool): ChatMe
result.role = MemberRole.ManageUsers
elif roles.contains(MemberRole.ModerateContent.int):
result.role = MemberRole.ModerateContent
elif roles.contains(MemberRole.TokenMaster.int):
result.role = MemberRole.TokenMaster
result.joined = joined

View File

@ -205,3 +205,22 @@ const asyncGetRevealedAccountsForAllMembersTask: Task = proc(argEncoded: string)
"communityId": arg.communityId,
"error": e.msg,
})
type
AsyncReevaluateCommunityMembersPermissionsArg = ref object of QObjectTaskArg
communityId: string
const asyncReevaluateCommunityMembersPermissionsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncReevaluateCommunityMembersPermissionsArg](argEncoded)
try:
let response = status_go.reevaluateCommunityMembersPermissions(arg.communityId)
arg.finish(%* {
"communityId": arg.communityId,
"response": response,
"error": "",
})
except Exception as e:
arg.finish(%* {
"communityId": arg.communityId,
"error": e.msg,
})

View File

@ -56,6 +56,8 @@ type TokenPermissionType* {.pure.}= enum
BecomeMember = 2,
View = 3,
ViewAndPost = 4,
BecomeTokenMaster = 5,
BecomeTokenOwner = 6
type TokenType* {.pure.}= enum
Unknown = 0,

View File

@ -677,7 +677,8 @@ QtObject:
let communities = parseCommunities(responseObj["communities"])
for community in communities:
self.communities[community.id] = community
if community.memberRole == MemberRole.Owner or community.memberRole == MemberRole.Admin:
if community.memberRole == MemberRole.Owner or
community.memberRole == MemberRole.Admin or community.memberRole == MemberRole.TokenMaster:
self.communities[community.id].pendingRequestsToJoin = self.allPendingRequestsToJoinForCommunity(community.id)
self.communities[community.id].declinedRequestsToJoin = self.declinedRequestsToJoinForCommunity(community.id)
self.communities[community.id].canceledRequestsToJoin = self.canceledRequestsToJoinForCommunity(community.id)
@ -2079,3 +2080,22 @@ QtObject:
))
except Exception as e:
error "error while getting the community members' revealed addressesses", msg = e.msg
proc asyncReevaluateCommunityMembersPermissions*(self: Service, communityId: string) =
let arg = AsyncGetRevealedAccountsForAllMembersArg(
tptr: cast[ByteAddress](asyncReevaluateCommunityMembersPermissionsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncReevaluateCommunityMembersPermissionsCompleted",
communityId: communityId,
)
self.threadpool.start(arg)
proc onAsyncReevaluateCommunityMembersPermissionsCompleted*(self: Service, response: string) {.slot.} =
try:
let rpcResponseObj = response.parseJson
if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "":
raise newException(RpcException, rpcResponseObj["error"].getStr)
except Exception as e:
error "error while reevaluating community members permissions", msg = e.msg

View File

@ -593,7 +593,11 @@ QtObject:
proc contractOwnerName*(self: Service, contractOwnerAddress: string): string =
try:
return self.walletAccountService.getAccountByAddress(contractOwnerAddress).name
let res = self.walletAccountService.getAccountByAddress(contractOwnerAddress)
if res == nil:
error "getAccountByAddress result is nil"
return ""
return res.name
except RpcException:
error "Error getting contract owner name", message = getCurrentExceptionMsg()

View File

@ -78,6 +78,13 @@ proc checkPermissionsToJoinCommunity*(communityId: string, addresses: seq[string
"addresses": addresses
}])
proc reevaluateCommunityMembersPermissions*(
communityId: string,
): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("reevaluateCommunityMembersPermissions".prefix, %*[{
"communityId": communityId
}])
proc checkCommunityChannelPermissions*(communityId: string, chatId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("checkCommunityChannelPermissions".prefix, %*[{
"communityId": communityId,

View File

@ -55,7 +55,8 @@ StackLayout {
accessType: communityData.access
joinCommunity: true
amISectionAdmin: communityData.memberRole === Constants.memberRole.owner ||
communityData.memberRole === Constants.memberRole.admin
communityData.memberRole === Constants.memberRole.admin ||
communityData.memberRole === Constants.memberRole.tokenMaster
communityItemsModel: root.rootStore.communityItemsModel
requirementsMet: root.permissionsStore.allTokenRequirementsMet
requirementsCheckPending: root.rootStore.permissionsCheckOngoing
@ -114,7 +115,8 @@ StackLayout {
sectionItemModel: root.sectionItemModel
amIMember: chatItem.amIMember
amISectionAdmin: root.sectionItemModel.memberRole === Constants.memberRole.owner ||
root.sectionItemModel.memberRole === Constants.memberRole.admin
root.sectionItemModel.memberRole === Constants.memberRole.admin ||
root.sectionItemModel.memberRole === Constants.memberRole.tokenMaster
hasViewOnlyPermissions: root.permissionsStore.viewOnlyPermissionsModel.count > 0
hasViewAndPostPermissions: root.permissionsStore.viewAndPostPermissionsModel.count > 0
viewOnlyPermissionsModel: root.permissionsStore.viewOnlyPermissionsModel

View File

@ -154,7 +154,9 @@ Item {
currentFleet = root.chatSectionModule.getCurrentFleet()
isCommunityChat = root.chatSectionModule.isCommunity()
amIChatAdmin = obj.memberRole === Constants.memberRole.owner || obj.memberRole === Constants.memberRole.admin
amIChatAdmin = obj.memberRole === Constants.memberRole.owner ||
obj.memberRole === Constants.memberRole.admin ||
obj.memberRole === Constants.memberRole.tokenMaster
chatId = obj.itemId
chatName = obj.name
chatDescription = obj.description

View File

@ -152,15 +152,60 @@ Control{
verticalAlignment: Text.AlignVCenter
}
StatusListItemTag {
height: d.flowRowHeight
title: PermissionTypes.getName(root.permissionType)
asset.name: PermissionTypes.getIcon(root.permissionType)
asset.isImage: false
asset.bgColor: "transparent"
closeButtonVisible: false
titleText.color: Theme.palette.primaryColor1
titleText.font.pixelSize: d.tagTextPixelSize
ListModel {
id: tokenOwnerModel
ListElement { permissionType: PermissionTypes.Type.Owner }
ListElement { permissionType: PermissionTypes.Type.TokenMaster }
ListElement { permissionType: PermissionTypes.Type.Admin }
}
ListModel {
id: tokenMasterModel
ListElement { permissionType: PermissionTypes.Type.TokenMaster }
ListElement { permissionType: PermissionTypes.Type.Admin }
}
ListModel {
id: regularRoleModel
ListElement { permissionType: PermissionTypes.Type.None }
}
Repeater {
id: rolesRepeater
model: {
if (root.permissionType === PermissionTypes.Type.Owner)
return tokenOwnerModel
if (root.permissionType === PermissionTypes.Type.TokenMaster)
return tokenMasterModel
return regularRoleModel
}
Flow {
spacing: 6
StatusListItemTag {
height: d.flowRowHeight
title: PermissionTypes.getName(model.permissionType === PermissionTypes.Type.None
? root.permissionType : model.permissionType)
asset.name: PermissionTypes.getIcon(root.permissionType)
asset.isImage: false
asset.bgColor: "transparent"
closeButtonVisible: false
titleText.color: Theme.palette.primaryColor1
titleText.font.pixelSize: d.tagTextPixelSize
}
StatusBaseText {
height: d.flowRowHeight
visible: model.index < rolesRepeater.model.count - 1
font.pixelSize: d.itemTextPixelSize
text: qsTr("and")
verticalAlignment: Text.AlignVCenter
}
}
}
StatusBaseText {

View File

@ -6,7 +6,7 @@ import StatusQ.Core.Theme 0.1
QtObject {
enum Type {
None, Admin, Member, Read, ViewAndPost, Moderator
None, Admin, Member, Read, ViewAndPost, TokenMaster, Owner
}
enum State {
@ -16,15 +16,17 @@ QtObject {
function getName(type) {
switch (type) {
case PermissionTypes.Type.Admin:
return qsTr("Become admin")
return qsTr("Become an admin")
case PermissionTypes.Type.Member:
return qsTr("Become member")
case PermissionTypes.Type.Read:
return qsTr("View only")
case PermissionTypes.Type.ViewAndPost:
return qsTr("View and post")
case PermissionTypes.Type.Moderator:
return qsTr("Moderate")
case PermissionTypes.Type.TokenMaster:
return qsTr("Admin community tokens")
case PermissionTypes.Type.Owner:
return qsTr("Admin TokenMaster tokens")
}
return ""
@ -40,7 +42,9 @@ QtObject {
return "edit"
case PermissionTypes.Type.Read:
return "show"
case PermissionTypes.Type.Moderator:
case PermissionTypes.Type.TokenMaster:
return "arbitrator"
case PermissionTypes.Type.Owner:
return "arbitrator"
}
@ -58,8 +62,6 @@ QtObject {
return `${generalInfo}<br><br>${warningStyled} ${warningExplanation}`
case PermissionTypes.Type.Member:
return qsTr("Anyone who meets the requirements will be allowed to join your community")
case PermissionTypes.Type.Moderator:
return qsTr("Members who meet the requirements will be allowed to read, write, ban members and pin messages in the selected channels")
case PermissionTypes.Type.ViewAndPost:
return qsTr("Members who meet the requirements will be allowed to read and write in the selected channels")
case PermissionTypes.Type.Read:
@ -70,8 +72,8 @@ QtObject {
}
function isCommunityPermission(permissionType) {
return permissionType === PermissionTypes.Type.Admin
|| permissionType === PermissionTypes.Type.Member
return permissionType === PermissionTypes.Type.Admin || permissionType === PermissionTypes.Type.Member ||
permissionType === PermissionTypes.Type.TokenMaster || permissionType === PermissionTypes.Type.Owner
}
function getPermissionsCountLimit(permissionType) {

View File

@ -482,8 +482,6 @@ Rectangle {
return qsTr("View only")
case PermissionTypes.Type.ViewAndPost:
return qsTr("View & post")
case PermissionTypes.Type.Moderator:
return qsTr("Moderate")
default:
return "???"
}

View File

@ -41,7 +41,8 @@ Item {
readonly property bool isSectionAdmin:
communityData.memberRole === Constants.memberRole.owner ||
communityData.memberRole === Constants.memberRole.admin
communityData.memberRole === Constants.memberRole.admin ||
communityData.memberRole === Constants.memberRole.tokenMaster
signal infoButtonClicked
signal manageButtonClicked

View File

@ -38,7 +38,7 @@ StatusSectionLayout {
required property var walletAccountsModel // name, address, emoji, color
readonly property bool isOwner: community.memberRole === Constants.memberRole.owner
readonly property bool isAdmin: isOwner || community.memberRole === Constants.memberRole.admin
readonly property bool isAdmin: community.memberRole === Constants.memberRole.admin
readonly property bool isTokenMasterOwner: community.memberRole === Constants.memberRole.tokenMaster
readonly property bool isControlNode: community.isControlNode
@ -248,7 +248,7 @@ StatusSectionLayout {
bannedMembersModel: root.community.bannedMembers
pendingMemberRequestsModel: root.community.pendingMemberRequests
declinedMemberRequestsModel: root.community.declinedMemberRequests
editable: root.isAdmin
editable: root.isAdmin || root.isOwner || root.isTokenMasterOwner
memberRole: community.memberRole
communityName: root.community.name
@ -304,7 +304,7 @@ StatusSectionLayout {
readonly property int sectionKey: Constants.CommunitySettingsSections.MintTokens
readonly property string sectionName: qsTr("Tokens")
readonly property string sectionIcon: "token"
readonly property bool sectionEnabled: root.isOwner
readonly property bool sectionEnabled: true
readonly property CommunityTokensStore communityTokensStore:
rootStore.communityTokensStore
@ -418,13 +418,13 @@ StatusSectionLayout {
readonly property int sectionKey: Constants.CommunitySettingsSections.Airdrops
readonly property string sectionName: qsTr("Airdrops")
readonly property string sectionIcon: "airdrop"
readonly property bool sectionEnabled: root.isOwner
readonly property bool sectionEnabled: true
communityDetails: d.communityDetails
// Profile type
isOwner: root.isOwner
isTokenMasterOwner: false // TODO: Backend
isTokenMasterOwner: root.isTokenMasterOwner
isAdmin: root.isAdmin
// Owner and TMaster properties
@ -540,6 +540,7 @@ StatusSectionLayout {
readonly property string color: root.community.color
readonly property bool owner: root.community.memberRole === Constants.memberRole.owner
readonly property bool admin: root.community.memberRole === Constants.memberRole.admin
readonly property bool tokenMaster: root.community.memberRole === Constants.memberRole.tokenMaster
}
function goTo(section: int, subSection: int) {

View File

@ -98,7 +98,10 @@ StatusScrollView {
? channelsSelectionModel : communityItemModel
isPrivate: model.isPrivate
showButtons: root.communityDetails.owner || (root.communityDetails.admin && model.permissionType !== PermissionTypes.Type.Admin)
showButtons: (model.permissionType !== PermissionTypes.Type.TokenMaster &&
model.permissionType !== PermissionTypes.Type.Owner) &&
(root.communityDetails.owner ||
((root.communityDetails.admin || root.communityDetails.tokenMaster) && model.permissionType !== PermissionTypes.Type.Admin))
onEditClicked: root.editPermissionRequested(model.index)
onDuplicateClicked: root.duplicatePermissionRequested(model.index)

View File

@ -5,7 +5,8 @@ import utils 1.0
ShowcaseDelegate {
title: !!showcaseObj && !!showcaseObj.name ? showcaseObj.name : ""
secondaryTitle: !!showcaseObj && (showcaseObj.memberRole === Constants.memberRole.owner ||
showcaseObj.memberRole === Constants.memberRole.admin) ? qsTr("Admin") : qsTr("Member")
showcaseObj.memberRole === Constants.memberRole.admin ||
showcaseObj.memberRole === Constants.memberRole.tokenMaster) ? qsTr("Admin") : qsTr("Member")
hasImage: !!showcaseObj && !!showcaseObj.image
icon.name: !!showcaseObj ? showcaseObj.name : ""

View File

@ -113,7 +113,8 @@ Control {
components: [
StatusIcon {
visible: model.memberRole === Constants.memberRole.owner ||
model.memberRole === Constants.memberRole.admin
model.memberRole === Constants.memberRole.admin ||
model.memberRole === Constants.memberRole.tokenMaster
anchors.verticalCenter: parent.verticalCenter
icon: "crown"
color: Theme.palette.directColor1

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 524c21834b7249da97c1c3bc712763a1d37b786d
Subproject commit 86d969727f7b5ff79f39e2246272cbf858cc752e