import slugify from '@matters/slugify' ; import { v4 } from 'uuid' ; import { APPRECIATION_PURPOSE , APPRECIATION_TYPES , ARTICLE_ACCESS_TYPE , ARTICLE_STATE , USER_STATE , } from 'common/enums' ; import { environment } from 'common/environment' ; import { ActionLimitExceededError , ArticleNotFoundError , AuthenticationError , ForbiddenByStateError , ForbiddenByTargetStateError , ForbiddenError , UserInputError , } from 'common/errors' ; import { fromGlobalId } from 'common/utils' ; import { gcp } from 'connectors' ; import { appreciationQueue } from 'connectors/queue' ; import { MutationToAppreciateArticleResolver } from 'definitions' ; const resolver = async ( root , { input : { id , amount , token , superLike } } , { viewer , dataSources : { atomService , userService , articleService , draftService , paymentService , systemService , } , } ) => { if ( ! viewer . id ) { throw new AuthenticationError ( 'visitor has no permission' ) ; } if ( [ USER_STATE . archived , USER_STATE . banned , USER_STATE . frozen ] . includes ( viewer . state ) ) { throw new ForbiddenByStateError ( `${ viewer . state } user has no permission` ) ; } if ( ! viewer . likerId ) { throw new ForbiddenError ( 'viewer has no liker id' ) ; } if ( ! amount || amount <= 0 ) { throw new UserInputError ( 'invalid amount' ) ; } const { id : dbId } = fromGlobalId ( id ) ; const article = await atomService . findFirst ( { table : 'article' , where : { id : dbId , state : ARTICLE_STATE . active } , } ) ; if ( ! article ) { throw new ArticleNotFoundError ( 'target article does not exists' ) ; } const node = await draftService . baseFindById ( article . draftId ) ; if ( ! node ) { throw new ArticleNotFoundError ( 'target article linked draft does not exists' ) ; } const isAuthor = article . authorId === viewer . id ; if ( isAuthor && ! superLike ) { throw new ForbiddenError ( 'cannot appreciate your own article' ) ; } const author = await userService . dataloader . load ( article . authorId ) ; if ( ! author ) { throw new ForbiddenError ( 'author has no liker id' ) ; } if ( author . state === USER_STATE . frozen ) { throw new ForbiddenByTargetStateError ( `cannot appreciate ${ author . state } user` ) ; } const articleCircle = await articleService . findArticleCircle ( article . id ) ; if ( articleCircle && ! isAuthor ) { const isCircleMember = await paymentService . isCircleMember ( { userId : viewer . id , circleId : articleCircle . circleId , } ) ; const isPaywall = articleCircle . access === ARTICLE_ACCESS_TYPE . paywall ; if ( isPaywall && ! isCircleMember ) { throw new ForbiddenError ( 'only circle members have the permission' ) ; } } const isBlocked = await userService . blocked ( { userId : article . authorId , targetId : viewer . id , } ) ; if ( isBlocked ) { throw new ForbiddenError ( 'viewer is blocked by target author' ) ; } /**
     * Super Like
     */ if ( superLike ) { const liker = await userService . findLiker ( { userId : viewer . id } ) ; if ( ! liker || ! author ) { throw new ForbiddenError ( 'viewer or author has no liker id' ) ; } const slug = slugify ( node . title ) ; const canSuperLike = await userService . likecoin . canSuperLike ( { liker , url : `${ environment . siteDomain }/@${ author . userName }/${ slug }-${ node . mediaHash }` , likerIp : viewer . ip , userAgent : viewer . userAgent , } ) ; if ( ! canSuperLike ) { throw new ForbiddenError ( 'cannot super like' ) ; } await userService . likecoin . superlike ( { liker , likerIp : viewer . ip , userAgent : viewer . userAgent , authorLikerId : author . likerId , url : `${ environment . siteDomain }/@${ author . userName }/${ slug }-${ node . mediaHash }` , } ) ; const appreciation = { senderId : viewer . id , recipientId : article . authorId , referenceId : article . id , purpose : APPRECIATION_PURPOSE . superlike , type : APPRECIATION_TYPES . like , } ; await atomService . create ( { table : 'appreciation' , data : { ... appreciation , uuid : v4 ( ) , amount } , } ) ; return node ; } /**
     * Like
     */ const appreciateLeft = await articleService . appreciateLeftByUser ( { articleId : dbId , userId : viewer . id , } ) ; if ( appreciateLeft <= 0 ) { throw new ActionLimitExceededError ( 'too many appreciations' ) ; } const validAmount = Math . min ( amount , appreciateLeft ) ; const feature = await systemService . getFeatureFlag ( 'verify_appreciate' ) ; if ( feature && ( await systemService . isFeatureEnabled ( feature . flag , viewer ) ) ) { const isHuman = await gcp . recaptcha ( { token , ip : viewer . ip } ) ; if ( ! isHuman ) { throw new ForbiddenError ( 'appreciate via script is not allowed' ) ; } } appreciationQueue . appreciate ( { amount : validAmount , articleId : article . id , senderId : viewer . id , senderIP : viewer . ip , userAgent : viewer . userAgent , } ) ; return node ; } ; export default resolver ;