import "reflect-metadata" ; import crypto from "crypto" ; import { ObjectID , ObjectId } from "mongodb" ; import { Context } from "../context.type" ; import { Ref } from "@typegoose/typegoose" ; import { ApolloError } from "apollo-server-express" ; import { Arg , Args , ArgsType , Authorized , Ctx , Field , Mutation , Query , Resolver , UseMiddleware , } from "type-graphql" ; import RoomModel from "../../entities/Room" ; import RateLimit from "../rate-limiter-middleware" ; import UserModel , { User } from "../../entities/User" ; import sendNotification from "../../utils/sendNotification" ; import { NOTIFICATION_TYPE } from "../../entities/Notification" ; import InvitationModel , { Invitation } from "../../entities/Invitation" ; import { InvitationLinkResult , InvitationDetails } from "./invitation-types" ; @ ArgsType ( ) export class inviteMembersArgs { @ Field ( { nullable : false } ) roomId : ObjectID ; @ Field ( ( ) => [ ObjectID ] ) members : ObjectID [ ] ; } @ Resolver ( ( ) => Invitation ) class InvitationResolver { @ Authorized ( ) @ Query ( ( ) => InvitationDetails ) async getInvitationInfo ( @ Arg ( "token" , { nullable : false } ) token , @ Ctx ( ) context ) { const invite = await InvitationModel . findOne ( { token : token , } ) . populate ( "roomId" ) . populate ( "invitedBy" ) ; if ( ! invite ) { throw new ApolloError ( "Could not get invitation info" ) ; } const userid = new ObjectID ( context . currentUser . id ) ; const currentUserInvite = userid . equals ( invite . userId as ObjectId ) ; if ( ! currentUserInvite && ! invite . isPublic ) { throw new ApolloError ( "Could not get invitation info" ) ; } return { id : invite . id , room : invite . roomId , invitedBy : invite . invitedBy , isPublic : invite . isPublic , createdAt : invite . createdAt , } ; } @ Authorized ( ) @ Mutation ( ( ) => InvitationLinkResult ) async createInvitationLink ( @ Arg ( "roomId" , { nullable : false } ) roomId , @ Ctx ( ) context ) { const existingInvitation = await InvitationModel . findOne ( { roomId : roomId , invitedBy : context . currentUser . id , isPublic : true , } ) ; const baseURL = process . env . NODE_ENV !== "production" ? "http://localhost:3000" : "https://convoychat.herokuapp.com" ; if ( existingInvitation ) { return { link : `${ baseURL }/invitation/${ existingInvitation . token }` , } ; } const validRoom = await RoomModel . findOne ( { _id : roomId } ) ; if ( ! validRoom ) throw new Error ( "Not a valid room" ) ; const token = crypto . randomBytes ( 16 ) . toString ( "hex" ) ; const invite = new InvitationModel ( { isPublic : true , roomId : roomId , invitedBy : context . currentUser . id , token : token , } ) ; await invite . save ( ) ; return { link : `${ baseURL }/invitation/${ token }` } ; } @ Authorized ( ) @ UseMiddleware ( RateLimit ( { limit : 15 } ) ) @ Mutation ( ( ) => [ Invitation ] ) async inviteMembers ( @ Args ( ) { roomId , members } , @ Ctx ( ) context ) { try { const user = await UserModel . findOne ( { _id : context . currentUser . id , rooms : { $in : [ roomId ] } , } ) ; if ( ! user ) { throw new ApolloError ( "You are not a member of room, Cannot invite members" ) ; } let token = null ; const invitations = members . map ( memberId => { token = crypto . randomBytes ( 16 ) . toString ( "hex" ) ; const invite = new InvitationModel ( { roomId : roomId , userId : memberId , invitedBy : context . currentUser . id , token : token , } ) ; return invite . save ( ) ; } ) ; const savedInvites = await Promise . all ( invitations ) ; const foundRoom = await RoomModel . findOne ( { _id : roomId } ) ; const notifications = members . map ( async ( id , index ) => { return sendNotification ( { context : context , sender : context . currentUser . id , receiver : id , type : NOTIFICATION_TYPE . INVITATION , payload : { userId : id , roomName : foundRoom . name , roomId : roomId , invitedBy : context . currentUser . id , token : savedInvites [ index ] . token , } , } ) ; } ) ; await Promise . all ( notifications ) ; return savedInvites ; } catch ( err ) { console . log ( err ) ; throw new ApolloError ( err ) ; } } @ Authorized ( ) @ Mutation ( ( ) => Boolean ) async acceptInvitation ( @ Arg ( "token" , { nullable : false } ) token , @ Ctx ( ) context ) { const invitation = await InvitationModel . findOne ( { token : token , } ) ; if ( ! invitation ) throw new ApolloError ( "Invalid Invitation" ) ; let userToAdd = null ; if ( invitation . isPublic === true ) { userToAdd = context . currentUser . id ; } if ( invitation . isPublic === false && `${ invitation . userId }` === `${ context . currentUser . id }` ) { userToAdd = invitation . userId ; } if ( ! userToAdd ) { throw new ApolloError ( "Something went wrong while accepting invite" ) ; } if ( invitation . uses . length >= invitation . maxUses ) { await InvitationModel . findOneAndRemove ( { token : token , } ) ; throw new ApolloError ( "Maximum invitation usage limit exceeded" ) ; } const room = await RoomModel . findOneAndUpdate ( { _id : invitation . roomId } , { $addToSet : { members : userToAdd } } , { new : true } ) ; if ( ! room ) throw new ApolloError ( "Could not add members to room" ) ; await UserModel . update ( { _id : userToAdd } , { $addToSet : { rooms : room . id } } , { new : true } ) ; if ( ! invitation . isPublic ) { await InvitationModel . findOneAndRemove ( { token : token } ) ; } else { await InvitationModel . findOneAndUpdate ( { token : token } , { $addToSet : { uses : userToAdd } } ) ; } return true ; } } export default InvitationResolver ;