Skip to content

Commit

Permalink
Merge pull request #4150 from ondratra/carthage_qn_channel_agent_perm…
Browse files Browse the repository at this point in the history
…issions

qn - channel agent permissions I
  • Loading branch information
mnaamani authored Sep 2, 2022
2 parents 7e1ba86 + 1a3798c commit 0582100
Show file tree
Hide file tree
Showing 25 changed files with 822 additions and 74 deletions.
2 changes: 1 addition & 1 deletion cli/src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export type VideoSubtitleInputParameters = Omit<ISubtitleMetadata, 'newAsset'> &
export type ChannelCreationInputParameters = Omit<IChannelMetadata, 'coverPhoto' | 'avatarPhoto'> & {
coverPhotoPath?: string
avatarPhotoPath?: string
collaborators?: { memberId: number; channelAgentPermissions: ChannelActionPermission['type'][] }[]
collaborators?: { memberId: number; permissions: ChannelActionPermission['type'][] }[]
privilegeLevel?: number
}

Expand Down
4 changes: 1 addition & 3 deletions cli/src/commands/content/createChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ export default class CreateChannelCommand extends UploadCommandBase {
storageBuckets,
distributionBuckets,
meta: metadataToBytes(ChannelMetadata, meta),
collaborators: new Map(
collaborators?.map(({ memberId, channelAgentPermissions }) => [memberId, channelAgentPermissions])
),
collaborators: new Map(collaborators?.map(({ memberId, permissions }) => [memberId, permissions])),
})

this.jsonPrettyPrint(JSON.stringify({ assets: assets?.toJSON(), metadata: meta, collaborators }))
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/content/updateChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export default class UpdateChannelCommand extends UploadCommandBase {
assetsToRemove,
newMeta: serializedMeta.length ? serializedMeta : null,
collaborators: collaborators?.length
? new Map(collaborators?.map(({ memberId, channelAgentPermissions }) => [memberId, channelAgentPermissions]))
? new Map(collaborators?.map(({ memberId, permissions }) => [memberId, permissions]))
: null,
})
this.jsonPrettyPrint(
Expand Down
2 changes: 1 addition & 1 deletion cli/src/schemas/ContentDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const ChannelCreationInputSchema: JsonSchema<ChannelCreationInputParamete
type: 'object',
properties: {
memberId: { type: 'integer' },
channelAgentPermissions: {
permissions: {
type: 'array',
items: {
type: 'string',
Expand Down
1 change: 1 addition & 0 deletions query-node/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ yarn
ln -s ../../../../../node_modules/typeorm/cli.js ./generated/graphql-server/node_modules/.bin/typeorm

yarn workspace query-node codegen

yarn workspace query-node build

yarn workspace query-node-mappings build
Expand Down
50 changes: 42 additions & 8 deletions query-node/mappings/src/content/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ChannelMetadata, ChannelModeratorRemarked, ChannelOwnerRemarked } from
import { ChannelId, DataObjectId } from '@joystream/types/primitives'
import {
Channel,
Collaborator,
ContentActor,
ContentActorCurator,
ContentActorMember,
Expand Down Expand Up @@ -38,7 +39,11 @@ import {
convertContentActor,
processChannelMetadata,
unsetAssetRelations,
mapAgentPermission,
} from './utils'
import { BTreeMap, BTreeSet, u64 } from '@polkadot/types'
// Joystream types
import { PalletContentChannelActionPermission } from '@polkadot/types/lookup'

export async function content_ChannelCreated(ctx: EventContext & StoreContext): Promise<void> {
const { store, event } = ctx
Expand All @@ -58,9 +63,6 @@ export async function content_ChannelCreated(ctx: EventContext & StoreContext):
// prepare channel owner (handles fields `ownerMember` and `ownerCuratorGroup`)
...(await convertChannelOwnerToMemberOrCuratorGroup(store, owner)),

collaborators: Array.from(channelCreationParameters.collaborators).map(
(id) => new Membership({ id: id[0].toString() })
),
rewardAccount: rewardAccount.toString(),
channelStateBloatBond: channelStateBloatBond.amount,
})
Expand All @@ -87,6 +89,9 @@ export async function content_ChannelCreated(ctx: EventContext & StoreContext):
// save entity
await store.save<Channel>(channel)

// update channel permissions
await updateChannelAgentsPermissions(store, channel, channelCreationParameters.collaborators)

// emit log event
logger.info('Channel has been created', { id: channel.id })
}
Expand Down Expand Up @@ -128,14 +133,14 @@ export async function content_ChannelUpdated(ctx: EventContext & StoreContext):
await processChannelMetadata(ctx, channel, newMetadata, storageDataObjectParams)
}

const newCollaborators = channelUpdateParameters.collaborators.unwrapOr(undefined)
if (newCollaborators) {
channel.collaborators = Array.from(newCollaborators).map((id) => new Membership({ id: id[0].toString() }))
}

// save channel
await store.save<Channel>(channel)

// update channel permissions
if (channelUpdateParameters.collaborators.isSome) {
await updateChannelAgentsPermissions(store, channel, channelUpdateParameters.collaborators.unwrap())
}

// emit log event
logger.info('Channel has been updated', { id: channel.id })
}
Expand Down Expand Up @@ -276,6 +281,35 @@ export async function content_ChannelAgentRemarked(ctx: EventContext & StoreCont
}
}

async function updateChannelAgentsPermissions(
store: DatabaseManager,
channel: Channel,
collaboratorsPermissions: BTreeMap<u64, BTreeSet<PalletContentChannelActionPermission>>
) {
// safest way to update permission is to delete existing and creating new ones

// delete existing agent permissions
const collaborators = await store.getMany(Collaborator, {
where: { channel: { id: channel.id.toString() } },
})
for (const agentPermissions of collaborators) {
await store.remove(agentPermissions)
}

// create new records for privledged members
for (const [memberId, permissions] of Array.from(collaboratorsPermissions)) {
const permissionsArray = Array.from(permissions)

const collaborator = new Collaborator({
channel: new Channel({ id: channel.id.toString() }),
member: new Membership({ id: memberId.toString() }),
permissions: Array.from(permissions).map(mapAgentPermission),
})

await store.save(collaborator)
}
}

async function processOwnerRemark(
store: DatabaseManager,
event: SubstrateEvent,
Expand Down
58 changes: 43 additions & 15 deletions query-node/mappings/src/content/curatorGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
eslint-disable @typescript-eslint/naming-convention
*/
import { DatabaseManager, EventContext, StoreContext } from '@joystream/hydra-common'
import { Curator, CuratorGroup } from 'query-node/dist/model'
import { Curator, CuratorGroup, CuratorAgentPermissions } from 'query-node/dist/model'
import { Content } from '../../generated/types'
import { inconsistentState, logger } from '../common'
import { mapAgentPermission } from './utils'
import { BTreeSet } from '@polkadot/types'
// Joystream types
import { PalletContentChannelActionPermission } from '@polkadot/types/lookup'

async function getCurator(store: DatabaseManager, curatorId: string): Promise<Curator | undefined> {
const existingCurator = await store.get(Curator, {
Expand Down Expand Up @@ -75,7 +79,7 @@ export async function content_CuratorGroupStatusSet({ store, event }: EventConte

export async function content_CuratorAdded({ store, event }: EventContext & StoreContext): Promise<void> {
// read event data
const [curatorGroupId, curatorId] = new Content.CuratorAddedEvent(event).params
const [curatorGroupId, curatorId, permissions] = new Content.CuratorAddedEvent(event).params

// load curator group
const curatorGroup = await store.get(CuratorGroup, {
Expand All @@ -91,12 +95,12 @@ export async function content_CuratorAdded({ store, event }: EventContext & Stor
// load curator
const curator = await ensureCurator(store, curatorId.toString())

// update curator group
curatorGroup.curators.push(curator)

// save curator group
await store.save<CuratorGroup>(curatorGroup)

// update curator permissions
await updateCuratorAgentPermissions(store, curatorGroup, curator, permissions)

// emit log event
logger.info('Curator has been added to curator group', { id: curatorGroupId, curatorId })
}
Expand All @@ -116,19 +120,43 @@ export async function content_CuratorRemoved({ store, event }: EventContext & St
return inconsistentState('Non-existing curator group removal requested', curatorGroupId)
}

const curatorIndex = curatorGroup.curators.findIndex((item) => item.id.toString() === curatorId.toString())
// load curator
const curator = await ensureCurator(store, curatorId.toString())

// ensure curator group exists
if (curatorIndex < 0) {
return inconsistentState('Non-associated curator removal from curator group requested', curatorId)
// update curator permissions
await updateCuratorAgentPermissions(store, curatorGroup, curator)

// emit log event
logger.info('Curator has been removed from curator group', { id: curatorGroupId, curatorId })
}

async function updateCuratorAgentPermissions(
store: DatabaseManager,
curatorGroup: CuratorGroup,
curator: Curator,
permissions?: BTreeSet<PalletContentChannelActionPermission>
) {
// safest way to update permission is to delete existing and creating new ones

// delete existing agent permissions
const existingAgentPermissions = await store.getMany(CuratorAgentPermissions, {
where: {
curatorGroup: { id: curatorGroup.id.toString() },
curator: { id: curator.id.toString() },
},
})
for (const agentPermissions of existingAgentPermissions) {
await store.remove(agentPermissions)
}

// update curator group
curatorGroup.curators.splice(curatorIndex, 1)
const permissionsArray = Array.from(permissions || [])

// save curator group
await store.save<CuratorGroup>(curatorGroup)
// create new records for privledged members
const curatorAgentPermissions = new CuratorAgentPermissions({
curatorGroup: new CuratorGroup({ id: curatorGroup.id.toString() }),
curator: new Curator({ id: curator.id.toString() }),
permissions: permissionsArray.map(mapAgentPermission),
})

// emit log event
logger.info('Curator has been removed from curator group', { id: curatorGroupId, curatorId })
await store.save(curatorAgentPermissions)
}
5 changes: 5 additions & 0 deletions query-node/mappings/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
PalletContentChannelOwner as ChannelOwner,
PalletContentPermissionsContentActor as ContentActor,
PalletContentStorageAssetsRecord as StorageAssets,
PalletContentChannelActionPermission,
} from '@polkadot/types/lookup'
import { DecodedMetadataObject } from '@joystream/metadata-protobuf/types'
import BN from 'bn.js'
Expand Down Expand Up @@ -684,3 +685,7 @@ export async function unsetAssetRelations(store: DatabaseManager, dataObject: St
// remove data object
await store.remove<StorageDataObject>(dataObject)
}

export function mapAgentPermission(permission: PalletContentChannelActionPermission): string {
return permission.toString()
}
18 changes: 16 additions & 2 deletions query-node/schemas/content.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ type Channel @entity {
"Number of the block the channel was created in"
createdInBlock: Int!

"List of channel collaborators (members)"
collaborators: [Membership!]
"List of channel collaborators with their permissions"
collaborators: [Collaborator!]! @derivedFrom(field: "channel")

"List of members blocked from from participating on any video of the channel."
bannedMembers: [Membership!]
Expand Down Expand Up @@ -380,3 +380,17 @@ type Comment @entity {
"The event the comment was moderated in (if any)"
moderatedInEvent: CommentModeratedEvent @derivedFrom(field: "comment")
}

type Collaborator @entity {
"Relevant channel"
channel: Channel!

"Related member"
member: Membership!

# unfortunately permissions can't be modeled as ist of enums
# `permissions: [ChannelActionPermission!]!`
# rework it after this feature is available https://github.com/Joystream/hydra/issues/507
"List of member's permissions"
permissions: [String!]!
}
23 changes: 19 additions & 4 deletions query-node/schemas/contentNft.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ type CuratorGroup @entity {
"Is group active or not"
isActive: Boolean!

"Curators belonging to this group"
curators: [Curator!]! @derivedFrom(field: "curatorGroups")

"Channels curated by this group"
channels: [Channel!]! @derivedFrom(field: "ownerCuratorGroup")

"NFTs owned by this member in various channels."
nftCollectorInChannels: [ChannelNftCollectors!]! @derivedFrom(field: "curatorGroup")
# permissionsByLevel: ModerationPermissionsByLevel<T>,

"Curator belonging to this group with their permissions"
curators: [CuratorAgentPermissions!]! @derivedFrom(field: "curatorGroup")
}

type Curator @entity {
Expand All @@ -48,7 +48,8 @@ type Curator @entity {
"Type needs to have at least one non-relation entity. This value is not used."
dummy: Int

curatorGroups: [CuratorGroup!]!
"Curator belonging to this group with their permissions"
curatorGroups: [CuratorAgentPermissions!]! @derivedFrom(field: "curator")
}

# NFT in name can't be UPPERCASE because it causes codegen errors
Expand Down Expand Up @@ -263,3 +264,17 @@ type Bid @entity {
}

# TODO entity for (cancelable) offers; will be needed to see history of offers

type CuratorAgentPermissions @entity {
"Relevant channel"
curatorGroup: CuratorGroup!

"Related member"
curator: Curator!

# unfortunately permissions can't be modeled as list of enums
# `permissions: [ChannelActionPermission!]!`
# rework it after this feature is available https://github.com/Joystream/hydra/issues/507
"List of member's permissions"
permissions: [String!]!
}
4 changes: 2 additions & 2 deletions query-node/schemas/membership.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ type Membership @entity {
"Content channels the member owns"
channels: [Channel!] @derivedFrom(field: "ownerMember")

"List of channels the member has collaborator access to"
collaboratorInChannels: [Channel!] @derivedFrom(field: "collaborators")
"List of channel collaborators with their permissions"
collaboratorInChannels: [Collaborator!]! @derivedFrom(field: "member")

# Required for Channel->bannedMembers Many-to-Many relationship
"List of channels the member is baned from participating in"
Expand Down
26 changes: 26 additions & 0 deletions tests/network-tests/src/QueryNodeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,14 @@ import {
GetDataObjectsByVideoIdQueryVariables,
GetDataObjectsByVideoId,
StorageDataObjectFieldsFragment,
CuratorAgentPermissionsFieldsFragment,
GetCuratorPermissionsByIdAndGroupId,
GetCuratorPermissionsByIdAndGroupIdQuery,
GetCuratorPermissionsByIdAndGroupIdQueryVariables,
CollaboratorsFieldsFragment,
GetCollaboratorsByChannelId,
GetCollaboratorsByChannelIdQuery,
GetCollaboratorsByChannelIdQueryVariables,
GetChannelDeletedByModeratorEventsByEventIdsQuery,
GetChannelDeletedByModeratorEventsByEventIdsQueryVariables,
GetChannelDeletedByModeratorEventsByEventIds,
Expand Down Expand Up @@ -1278,6 +1286,24 @@ export class QueryNodeApi {
)
}

public async getCuratorPermissionsByIdAndGroupId(
curatorGroupId: string,
curatorId: string
): Promise<Maybe<CuratorAgentPermissionsFieldsFragment>> {
return this.firstEntityQuery<
GetCuratorPermissionsByIdAndGroupIdQuery,
GetCuratorPermissionsByIdAndGroupIdQueryVariables
>(GetCuratorPermissionsByIdAndGroupId, { curatorGroupId, curatorId }, 'curatorAgentPermissions')
}

public async getCollaboratorsByChannelId(channelId: string): Promise<CollaboratorsFieldsFragment[]> {
return this.multipleEntitiesQuery<GetCollaboratorsByChannelIdQuery, GetCollaboratorsByChannelIdQueryVariables>(
GetCollaboratorsByChannelId,
{ channelId },
'collaborators'
)
}

public async getMembershipVerificationStatusUpdatedEvents(
events: EventDetails[]
): Promise<MemberVerificationStatusUpdatedEventFieldsFragment[]> {
Expand Down
Loading

0 comments on commit 0582100

Please sign in to comment.