Project
GraphQL 메시지 대화 삭제
프도의길
2023. 9. 20. 19:53
대화창 삭제 기능을 만들어 보겠습니다.
대화리스트 부분에 마우스 오른쪽 클릭시 삭제 부분 클릭시 삭제 하겠습니다.
먼저 백엔드 작업부터하겠습니다.
//backend src/graphql/typeDefs/conversation.ts
type Mutation {
deleteConversation(conversationId: String!): Boolean
}
type Subscription {
conversationDeleted: ConversationDeletedSubscriptionPayload
}
resolver도 만들어줍니다
//backend src/graphql/resolvers/conversations.ts
import { GraphQLError } from "graphql";
import { Prisma } from "@prisma/client";
import { withFilter } from "graphql-subscriptions";
import {
ConversationDeletedSubscriptionPayload,
ConversationPopulated,
ConversationUpdatedSubscriptionPayload,
GraphQLContext,
} from "./../../util/types";
import { userIsConversationPraticipant } from "../../util/functions";
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 GraphQLError("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 GraphQLError(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 GraphQLError("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 GraphQLError("Err creating conversation");
}
},
markConversationAsRead: async function (
_: any,
args: { userId: string; conversationId: string },
context: GraphQLContext
): Promise<boolean> {
const { session, prisma } = context;
const { userId, conversationId } = args;
if (!session?.user) {
throw new GraphQLError("Not authorized");
}
try {
const participant = await prisma.conversationParticipant.findFirst({
where: {
userId,
conversationId,
},
});
if (!participant) {
throw new GraphQLError("Participant entity not found");
}
await prisma.conversationParticipant.update({
where: {
id: participant.id,
},
data: {
hasSeenLatestMessage: true,
},
});
return true;
} catch (err: any) {
console.log("markConversationAsRead error", err);
throw new GraphQLError(err?.message);
}
},
deleteConversation: async function (
_: any,
args: { conversationId: string },
context: GraphQLContext
): Promise<boolean> {
const { session, prisma, pubsub } = context;
const { conversationId } = args;
if (!session?.user) {
throw new GraphQLError("Not authorized");
}
try {
const [deletedConversation] = await prisma.$transaction([
prisma.conversation.delete({
where: {
id: conversationId,
},
include: conversationPopulated,
}),
prisma.conversationParticipant.deleteMany({
where: {
conversationId,
},
}),
prisma.message.deleteMany({
where: {
conversationId,
},
}),
]);
console.log("deletedConversation", deletedConversation);
pubsub.publish("CONVERSATION_DELETED", {
conversationDeleted: deletedConversation,
});
return true;
} catch (err: any) {
console.log("deleteConversation error", err);
throw new GraphQLError(err?.message);
}
},
},
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 {
conversationCreated: { participants },
} = payload as any;
// const userIsParticipant = !!participants.find(
// (participant: { userId: string }) => participant.userId === userId
// );
const userIsParticipant = userIsConversationPraticipant(
participants,
session?.user?.id
);
return userIsParticipant;
}
),
},
conversationUpdated: {
subscribe: withFilter(
(_: any, __: any, context: GraphQLContext) => {
const { pubsub } = context;
return pubsub.asyncIterator(["CONVERSATION_UPDATED"]);
},
(
payload: ConversationUpdatedSubscriptionPayload,
_: any,
context: GraphQLContext
) => {
const { session } = context;
console.log("HE IS PALOAD", payload);
if (!session?.user) {
throw new GraphQLError("Not authorized");
}
const { id: userId } = session.user;
const {
conversationUpdated: {
conversation: { participants },
},
}: any = payload;
const userIsParticipant = userIsConversationPraticipant(
participants,
userId
);
return userIsParticipant;
}
),
},
conversationDeleted: {
subscribe: withFilter(
(_: any, __: any, context: GraphQLContext) => {
const { pubsub } = context;
return pubsub.asyncIterator(["CONVERSATION_DELETED"]);
},
(
payload: ConversationDeletedSubscriptionPayload,
_,
context: GraphQLContext
) => {
const { session } = context;
if (!session?.user) {
throw new GraphQLError("Not authorized");
}
const { id: userId } = session.user;
const {
conversationDeleted: { participants },
}: any = payload;
return userIsConversationPraticipant(participants, userId);
}
),
},
},
};
Mutation과 Subscribe에 삭제 기능을 추가합니다. 대화 삭제이기 때문에 transaction으로 conversation, conversationParticipant, message 를 모두 conversationId 요청받은걸로 삭제해줍니다.
//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
}
}
`,
deleteConversation: gql`
mutation deleteConversation($conversationId: String!) {
deleteConversation(conversationId: $conversationId)
}
`,
markConversationAsRead: gql`
mutation MarkConversationAsRead(
$userId: String!
$conversationId: String!
) {
markConversationAsRead(userId: $userId, conversationId: $conversationId)
}
`,
},
Subscriptions: {
conversationCreated: gql`
subscription ConversationCreated {
conversationCreated {
${ConversationFields}
}
}
`,
conversationUpdated: gql`
subscription ConversationUpdated {
conversationUpdated {
conversation{
${ConversationFields}
}
}
}
`,
conversationDeleted: gql`
subscription ConversationDeleted {
conversationDeleted {
id
}
}
`,
},
};
백엔드에 요청하게 프론트에서도 Mutation, Subscriptions을 추가해줍니다.
//frontend
const [deleteConversation] = useMutation<
{ deleteConversation: boolean },
{ conversationId: string }
>(ConversationOperations.Mutations.deleteConversation);
const onDeleteConversation = async (conversationId: string) => {
try {
toast.promise(
deleteConversation({
variables: {
conversationId,
},
update: () => {
router.replace(
typeof process.env.NEXT_PUBLIC_BASE_URL === "string"
? process.env.NEXT_PUBLIC_BASE_URL
: ""
);
},
}),
{
loading: "Deleting conversation",
success: "Conversation deleted",
error: "Failed to delete conversation",
}
);
} catch (error) {
console.log("onDeleteConversation error", error);
}
};
클릭버튼 함수 mutation을 만들어줍니다 그리고 나서 바로 실시간으로 통신하는
//frontend
useSubscription<ConversationDeletedData>(
ConversationOperations.Subscriptions.conversationDeleted,
{
onData: ({ client, data }) => {
console.log("HERE IS SUB DATA", data);
const { data: subscriptionData } = data;
if (!subscriptionData) return;
const existing = client.readQuery<ConversationsData>({
query: ConversationOperations.Queries.conversations,
});
if (!existing) return;
const { conversations } = existing;
const {
conversationDeleted: { id: deletedConversationId },
} = subscriptionData;
client.writeQuery<ConversationsData>({
query: ConversationOperations.Queries.conversations,
data: {
conversations: conversations.filter(
(conversation) => conversation.id !== deletedConversationId
),
},
});
},
}
);
subscription 해줍니다.
삭제 버튼 누르면 바로 삭제가 됩니다.