import { Collection , ComponentRequestTable , ConceptDb , ConceptServer , ConceptServerBuilder , Config , Context , getReturnFields , isSuccessfulContext , Validation } from '@deja-vu/concept-server' ; import { PubSub , withFilter } from 'graphql-subscriptions' ; import { IResolvers } from 'graphql-tools' ; import { ChatMessagesInput , CreateMessageInput , MessageDoc , NewChatMessagesInput , UpdateMessageInput } from './schema' ; import { v4 as uuid } from 'uuid' ; const pubsub = new PubSub ( ) ; const NEW_MESSAGE_TOPIC = 'new-message' ; const MS_IN_S = 1000 ; function dateToUnixTime ( date ) { return date . valueOf ( ) / MS_IN_S ; } interface MessageConfig extends Config { onlyAuthorCanEdit ? : boolean ; } class MessageValidation { static async messageExistsOrFails ( messages , id ) { return Validation . existsOrFail ( messages , id , 'Message' ) ; } } const componentRequestTable = { 'create-message' : ( extraInfo ) => `
    mutation CreateMessage($input: CreateMessageInput!) {
      createMessage(input: $input) ${ getReturnFields ( extraInfo ) }
    }
  ` , 'delete-message' : ( extraInfo ) => `
    mutation DeleteMessage($id: ID!) {
      deleteMessage(id: $id) ${ getReturnFields ( extraInfo ) }
    }
  ` , 'update-message' : ( extraInfo ) => { switch ( extraInfo . action ) { case 'update' : return `
          mutation UpdateMessage($input: UpdateMessageInput!) {
            updateMessage(input: $input) ${ getReturnFields ( extraInfo ) }
          }
        ` ; case 'load' : return `
          query Message($id: ID!) {
            message(id: $id) ${ getReturnFields ( extraInfo ) }
          }
        ` ; default : throw new Error ( 'Need to specify extraInfo.action' ) ; } } , 'show-chat' : ( extraInfo ) => { switch ( extraInfo . action ) { case 'subscribe' : return `subscription NewChatMessage($chatId: ID!) {
          newChatMessage(chatId: $chatId)
        }` ; case 'new' : return `
          query NewChatMessages($input: NewChatMessagesInput!) {
            newChatMessages(input: $input) ${ getReturnFields ( extraInfo ) }
          }
        ` ; default : return `
          query ChatMessages($input: ChatMessagesInput!) {
            chatMessages(input: $input) ${ getReturnFields ( extraInfo ) }
          }
        ` ; } } , 'show-message' : ( extraInfo ) => `
    query ShowMessage($id: ID!) {
      message(id: $id) ${ getReturnFields ( extraInfo ) }
    }
  ` } ; function resolvers ( db , config ) { const messages = db . collection ( 'messages' ) ; return { Query : { message : async ( _root , { id } ) => await messages . findOne ( { id } ) , chatMessages : async ( _root , { input } ) : Promise < MessageDoc [ ] > => { const limit = input . maxMessageCount ? input . maxMessageCount : 0 ; return ( await messages . findCursor ( { chatId : input . chatId } ) ) . sort ( { timestamp : 1 } ) . limit ( limit ) . toArray ( ) ; } , newChatMessages : async ( _root , { input } ) : Promise < MessageDoc [ ] > => ( await messages . findCursor ( { chatId : input . chatId , timestamp : { $gt : input . lastMessageTimestamp } } ) ) . sort ( { timestamp : 1 } ) . toArray ( ) } , Message : { id : ( message ) => message . id , content : ( message ) => message . content , timestamp : ( message ) => dateToUnixTime ( message . timestamp ) , authorId : ( message ) => message . authorId , chatId : ( message ) => message . chatId } , Mutation : { createMessage : async ( _root , { input } , context ) => { const message = { id : input . id ? input . id : uuid ( ) , content : input . content , timestamp : new Date ( ) , authorId : input . authorId , chatId : input . chatId } ; return await messages . insertOne ( context , message ) . then ( ( newMessage ) => { if ( isSuccessfulContext ( context ) ) { pubsub . publish ( NEW_MESSAGE_TOPIC , { newMessage } ) ; } return newMessage ; } ) ; } , updateMessage : async ( _root , { input } , context ) => { if ( config . onlyAuthorCanEdit ) { const message = await MessageValidation . messageExistsOrFails ( messages , input . id ) ; if ( message . authorId !== input . authorId ) { throw new Error ( 'Only the author of the message can edit it.' ) ; } } const updateOp = { $set : { content : input . content } } ; return await messages . updateOne ( context , { id : input . id } , updateOp ) ; } , deleteMessage : async ( _root , { id } , context ) => await messages . deleteOne ( context , { id } ) } , Subscription : { newChatMessage : { resolve : ( _payload , _args , _context , _info ) => { return true ; } , subscribe : withFilter ( ( ) => pubsub . asyncIterator ( NEW_MESSAGE_TOPIC ) , ( payload , variables ) => variables . chatId === payload . newMessage . chatId ) } } } ; } const chatConcept = new ConceptServerBuilder ( 'chat' ) . initDb ( ( db , _config ) : Promise < any > => { const messages = db . collection ( 'messages' ) ; return Promise . all ( [ messages . createIndex ( { id : 1 } , { unique : true , sparse : true } ) , messages . createIndex ( { chatId : 1 , timestamp : 1 } ) ] ) ; } ) . componentRequestTable ( componentRequestTable ) . resolvers ( resolvers ) . build ( ) ; chatConcept . start ( ) ;