Project
GraphQL 대화생성 Subscription
프도의길
2023. 8. 29. 21:17
대화생성 Subscription 만드는 방법을 하겠습니다.
https://www.apollographql.com/docs/apollo-server/data/subscriptions/
*참고자료
Subscriptions in Apollo Server
Persistent GraphQL read operations
www.apollographql.com
//backend src/graphql/typeDefs/conversation.ts
import { gql } from "apollo-server-core";
const typeDefs = gql`
scalar Date
type Mutation {
createConversation(participantIds: [String]): CreateConversationResponse
}
type CreateConversationResponse {
conversationId: String
}
type Conversation {
id: String
latestMessage: Message
participants: [Participant]
createdAt: Date
updatedAt: Date
}
type Participant {
id: String
user: User
hasSeenLatestMessage: Boolean
}
type Query {
conversations: [Conversation]
}
type Subscription {
conversationCreated: Conversation
}
`;
export default typeDefs;
type Subscription{}추가 합니다.
//backend src/graphql/resolvers/conversation.ts
import { GraphQLError } from "graphql";
import { Prisma } from "@prisma/client";
import { ApolloError } from "apollo-server-core";
import { withFilter } from "graphql-subscriptions";
import { ConversationPopulated, GraphQLContext } from "./../../util/types";
const resolvers = {
Query: {
conversations: async (
_: any,
args: any,
context: GraphQLContext
): Promise<Array<ConversationPopulated>> => {
console.log("CONVERSATIONS QUERY");
const { session, prisma } = context;
if (!session?.user) {
throw new ApolloError("Not authorized");
}
const {
user: { id: userId },
} = session;
try {
const conversations: any = await prisma.conversation.findMany({
/**
* Below has been confirmed to be the correct
* query by the Prisma team. Has been confirmed
* that there is an issue on their end
* Issue seems specific to Mongo
*/
// where: {
// participants: {
// some: {
// userId: {
// equals: userId,
// },
// },
// },
// },
include: conversationPopulated,
});
return conversations.filter(
(conversation: { participants: any[] }) =>
!!conversation.participants.find((p) => p.userId === userId)
);
} catch (error: any) {
console.log("conversations Err", error);
throw new ApolloError(error?.message);
}
},
},
Mutation: {
createConversation: async (
_: any,
args: { participantIds: Array<string> },
context: GraphQLContext
): Promise<{ conversationId: string }> => {
console.log("INSIDE CREATE CONVERSATION", args);
const { session, prisma, pubsub } = context;
const { participantIds } = args;
if (!session?.user) {
throw new ApolloError("Not authorized");
}
const {
user: { id: userId },
} = session;
try {
const conversation = await prisma.conversation.create({
data: {
participants: {
createMany: {
data: participantIds.map((id) => ({
userId: id,
hasSeenLatestMessage: id === userId,
})),
},
},
},
include: conversationPopulated,
});
pubsub.publish("CONVERSATION_CREATED", {
conversationCreated: conversation,
});
return {
conversationId: conversation.id,
};
} catch (error) {
console.log("CreateConversation Err", error);
throw new ApolloError("Err creating conversation");
}
},
},
Subscription: {
conversationCreated: {
// subscribe: (_: any, __: any, context: GraphQLContext) => {
// const { pubsub } = context;
// return pubsub.asyncIterator(["CONVERSATION_CREATED"]);
// },
subscribe: withFilter(
(_: any, __: any, context: GraphQLContext) => {
const { pubsub } = context;
return pubsub.asyncIterator(["CONVERSATION_CREATED"]);
},
(
payload: ConversationCreatedSubscriptionPayload,
_,
context: GraphQLContext
) => {
const { session } = context;
if (!session?.user) {
throw new GraphQLError("Not authorized");
}
const { id: userId } = session.user;
const {
conversationCreated: { participants },
} = payload as any;
const userIsParticipant = !!participants.find(
(participant: { userId: string }) => participant.userId === userId
);
return userIsParticipant;
}
),
},
},
};
export interface ConversationCreatedSubscriptionPayload {
conversationCreated: ConversationPopulated;
}
export const participantPopulated =
Prisma.validator<Prisma.ConversationParticipantInclude>()({
user: {
select: {
id: true,
username: true,
},
},
});
export const conversationPopulated =
Prisma.validator<Prisma.ConversationInclude>()({
participants: {
include: participantPopulated,
},
latestMessage: {
include: {
sender: {
select: {
id: true,
username: true,
},
},
},
},
});
export default resolvers;
Subscription{}을 추가해줍니다
//frontend src/graphql/operations/conversation.ts
import { gql } from "@apollo/client";
import { MessageFields } from "./messages";
const ConversationFields = `
id
updatedAt
participants {
user {
id
username
}
hasSeenLatestMessage
}
latestMessage {
${MessageFields}
}
`;
export default {
Queries: {
conversations: gql`
query Conversations {
conversations {
${ConversationFields}
}
}
`,
},
Mutations: {
createConversation: gql`
mutation CreateConversation($participantIds: [String]!) {
createConversation(participantIds: $participantIds) {
conversationId
}
}
`,
},
Subscriptions: {
conversationCreated: gql`
subscription ConversationCreated {
conversationCreated {
${ConversationFields}
}
}
`,
},
};
frontend에사도 Subscriontions:{}추가해줍니다
//frontend src/components/Chat/Conversations/ConversationWrapper.tsx
import { useQuery } from "@apollo/client";
import { Box } from "@chakra-ui/react";
import { Session } from "next-auth";
import ConversationList from "./ConversationList";
import ConversationOperations from "../../../graphql/operations/conversation";
import { ConversationsData } from "@/util/types";
// import { ConversationPopulated } from "../../../../../backend/src/util/types";
import { useEffect } from "react";
import { useRouter } from "next/router";
interface ConversationsWrapperProps {
session: Session;
}
interface ConversationProps {
conversations: {
id: string;
latestMessage: any;
participants: any[];
updateAt: string;
}[];
}
const ConversationsWrapper = ({ session }: ConversationsWrapperProps) => {
const {
data: conversationsData,
error: conversationsError,
loading: conversationsLoading,
subscribeToMore,
} = useQuery<ConversationProps>(ConversationOperations.Queries.conversations);
const router = useRouter();
const {
query: { conversationId },
} = router;
const onViewConversations = async (conversationId: string) => {
router.push({ query: { conversationId } });
};
const subscribeToNewConversations = () => {
subscribeToMore({
document: ConversationOperations.Subscriptions.conversationCreated,
updateQuery: (
prev,
{
subscriptionData,
}: {
subscriptionData: {
data: { conversationCreated: any };
};
}
) => {
if (!subscriptionData.data) return prev;
const newConversation = subscriptionData.data.conversationCreated;
return Object.assign({}, prev, {
conversations: [newConversation, ...prev.conversations],
});
},
});
};
useEffect(() => {
subscribeToNewConversations();
}, []);
console.log("HERE IS DATA", conversationsData);
return (
<Box
display={{ base: conversationId ? "none" : "flex", md: "flex" }}
width={{ base: "100%", md: "400px" }}
bg="#2A2A2A"
py={6}
px={3}
>
<ConversationList
session={session}
conversations={conversationsData?.conversations}
onViewConversation={onViewConversations}
/>
</Box>
);
};
export default ConversationsWrapper;
subscribeToNewConversations 을 추가해서 실시간 업데이트를 해줍니다
왼쪽은 크롬창, 오른쪽은 fire Fox창입니다
오른쪽창에서 대화 생성을 누르면
바로 대화생성이 업데이트가 됩니다.