import _difference from 'lodash/difference' ; import _some from 'lodash/some' ; import _uniq from 'lodash/uniq' ; import { CACHE_KEYWORD , DB_NOTICE_TYPE , NODE_TYPES , USER_STATE , } from 'common/enums' ; import { environment } from 'common/environment' ; import { AuthenticationError , ForbiddenByStateError , ForbiddenError , TagEditorsReachLimitError , TagNotFoundError , UserInputError , } from 'common/errors' ; import { fromGlobalId } from 'common/utils' ; import { GQLUpdateTagSettingType as UpdateType , MutationToUpdateTagSettingResolver , } from 'definitions' ; const resolver = async ( _ , { input : { id , type , editors } } , { viewer , dataSources : { notificationService , systemService , tagService , userService , } , } ) => { if ( ! viewer . id ) { throw new AuthenticationError ( 'viewer has no permission' ) ; } if ( viewer . state === USER_STATE . frozen ) { throw new ForbiddenByStateError ( `${ viewer . state } user has no permission` ) ; } const { id : tagId } = fromGlobalId ( id ) ; const tag = await tagService . baseFindById ( tagId ) ; if ( ! tag ) { throw new TagNotFoundError ( 'tag not found' ) ; } const { mattyId } = environment ; const isOwner = tag . owner === viewer . id ; const isMatty = viewer . id === mattyId ; let updatedTag ; switch ( type ) { case UpdateType . adopt : { const feature = await systemService . getFeatureFlag ( 'tag_adoption' ) ; if ( feature && ! ( await systemService . isFeatureEnabled ( feature . flag , viewer ) ) ) { throw new ForbiddenError ( 'viewer has no permission' ) ; } if ( tag . owner ) { throw new ForbiddenError ( 'viewer has no permission' ) ; } updatedTag = await tagService . baseUpdate ( tagId , { owner : viewer . id , editors : _uniq ( [ ... tag . editors , viewer . id ] ) , } ) ; await tagService . follow ( { targetId : tag . id , userId : viewer . id } ) ; notificationService . mail . sendAdoptTag ( { to : viewer . email , language : viewer . language , recipient : { displayName : viewer . displayName , userName : viewer . userName , } , tag : { id , content : tag . content } , } ) ; const participants = await tagService . findParticipants ( { id : tag . id } ) ; participants . map ( async ( participant ) => { await notificationService . trigger ( { event : DB_NOTICE_TYPE . tag_adoption , recipientId : participant . authorId , actorId : viewer . id , entities : [ { type : 'target' , entityTable : 'tag' , entity : tag , } , ] , } ) ; } ) ; break ; } case UpdateType . leave : { if ( ! tag . owner || ( tag . owner && ! isOwner ) ) { throw new ForbiddenError ( 'viewer has no permission' ) ; } const newEditors = isMatty ? undefined : ( tag . editors || [ ] ) . filter ( ( item ) => item !== viewer . id ) ; updatedTag = await tagService . baseUpdate ( tagId , { owner : null , editors : newEditors , } ) ; if ( newEditors && newEditors . length > 0 ) { newEditors . map ( ( editor ) => { notificationService . trigger ( { event : DB_NOTICE_TYPE . tag_leave , recipientId : editor , actorId : viewer . id , entities : [ { type : 'target' , entityTable : 'tag' , entity : tag , } , ] , } ) ; } ) ; } break ; } case UpdateType . add_editor : { if ( ! isOwner ) { throw new ForbiddenError ( 'viewer has no permission' ) ; } if ( ! editors || editors . length === 0 ) { throw new UserInputError ( 'editors are invalid' ) ; } const newEditors = ( editors . map ( ( editor ) => { const { id : editorId } = fromGlobalId ( editor ) ; if ( ! tag . editors . includes ( editorId ) ) { return editorId ; } } ) . filter ( ( editorId ) => editorId !== undefined ) as string [ ] ) || [ ] ; const dedupedEditors = _uniq ( [ ... tag . editors , ... newEditors ] ) ; if ( dedupedEditors . length > 6 ) { throw new TagEditorsReachLimitError ( 'number of editors reaches limit' ) ; } updatedTag = await tagService . baseUpdate ( tagId , { editors : dedupedEditors , } ) ; await Promise . all ( newEditors . map ( ( editorId ) => tagService . follow ( { targetId : tag . id , userId : editorId } ) ) ) ; const recipients = ( await userService . dataloader . loadMany ( newEditors ) ) as Array < Record < string , any > > ; recipients . map ( ( recipient ) => { notificationService . mail . sendAssignAsTagEditor ( { to : recipient . email , language : recipient . language , recipient : { displayName : recipient . displayName , userName : recipient . userName , } , sender : { displayName : viewer . displayName , userName : viewer . userName , } , tag : { id , content : tag . content } , } ) ; notificationService . trigger ( { event : DB_NOTICE_TYPE . tag_add_editor , recipientId : recipient . id , actorId : viewer . id , entities : [ { type : 'target' , entityTable : 'tag' , entity : tag , } , ] , } ) ; } ) ; break ; } case UpdateType . remove_editor : { if ( ! isOwner ) { throw new ForbiddenError ( 'viewer has no permission' ) ; } if ( ! editors || editors . length === 0 ) { throw new UserInputError ( 'editors are invalid' ) ; } const removeEditors = ( editors . map ( ( editor ) => { const { id : editorId } = fromGlobalId ( editor ) ; if ( editorId === tag . owner || editorId === mattyId ) { return ; } return editorId ; } ) . filter ( ( editorId ) => editorId !== undefined ) as string [ ] ) || [ ] ; updatedTag = await tagService . baseUpdate ( tagId , { editors : _difference ( tag . editors , removeEditors ) , } ) ; break ; } case UpdateType . leave_editor : { const isEditor = _some ( tag . editors , ( editor ) => editor === viewer . id ) ; if ( ! isEditor ) { throw new ForbiddenError ( 'viewer has no permission' ) ; } if ( isOwner || isMatty ) { throw new ForbiddenError ( 'viewer cannot leave' ) ; } updatedTag = await tagService . baseUpdate ( tagId , { editors : _difference ( tag . editors , [ viewer . id ] ) , } ) ; if ( tag . owner ) { notificationService . trigger ( { event : DB_NOTICE_TYPE . tag_leave_editor , recipientId : tag . owner , actorId : viewer . id , entities : [ { type : 'target' , entityTable : 'tag' , entity : tag , } , ] , } ) ; } break ; } default : { throw new UserInputError ( 'unknown update tag type' ) ; } } if ( updatedTag ) { updatedTag [ CACHE_KEYWORD ] = [ { id : viewer . id , type : NODE_TYPES . User } ] ; } return updatedTag ; } ; export default resolver ;