저번에 유저 검색하는 부분을 하였습니다 검색 한 유저와 대화를 할 수 있게 대화 생성을 만들어보겠습니다.
선택누르시 검색한 유저랑 채팅할 수 있는 대화방 생성을 만들어 보겠습니다.
먼저 schema.prisma에서 테이블을 만들어줍니다
generator client {
provider = "prisma-client-js"
binaryTargets = ["native","darwin","darwin-arm64","debian-openssl-1.1.x"]
}
datasource db {
provider = "mongodb"
url = env("MONGODB_URL")
}
model Account {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.String
access_token String? @db.String
expires_at Int?
token_type String?
scope String?
id_token String? @db.String
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(auto()) @map("_id") @db.ObjectId
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
email String? @unique
username String? @unique
conversations ConversationParticipant[]
messages Message[]
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model VerificationToken {
id String @id @default(auto()) @map("_id") @db.ObjectId
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
model Conversation {
id String @id @default(auto()) @map("_id") @db.ObjectId
messages Message[] @relation("conversationMessages")
participants ConversationParticipant[]
latestMessageId String? @unique
latestMessage Message? @relation(name: "latestConversationMessage", fields: [latestMessageId], references: [id], onUpdate: NoAction, onDelete: NoAction)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ConversationParticipant {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
user User @relation(fields:[userId], references:[id])
conversationId String
conversation Conversation @relation(fields:[conversationId], references:[id])
hasSeenLatestMessage Boolean
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Message {
id String @id @default(auto()) @map("_id") @db.ObjectId
conversation Conversation @relation(name: "conversationMessages", fields: [conversationId], references: [id])
conversationId String
senderId String
sender User @relation(fields:[senderId], references:[id])
body String
isLatestIn Conversation? @relation(name:"latestConversationMessage")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
다대다 형식으로 만들어줍니다 prisma를 수정할때 무조건 npx prisma generate --schema=src/prisma/schema.prisma 해줍니다.
//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,
});
return {
conversationId: conversation.id,
};
} catch (error) {
console.log("CreateConversation Err", error);
throw new ApolloError("Err creating conversation");
}
},
},
};
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;
대화 id conversationId를 리턴해준다
//frontend src/compoents/Chat/Conversations/Modal/Modal.tsx
import {
Button,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Stack,
Text,
} from "@chakra-ui/react";
import UserOperations from "../../../../graphql/operations/user";
import ConversationOperations from "../../../../graphql/operations/conversation";
import React, { useCallback, useState } from "react";
import { Observable, useLazyQuery, useMutation } from "@apollo/client";
import {
CreateConversationData,
SearchedUser,
SearchUsersData,
SearchUsersInput,
} from "@/util/types";
import UserSearchList from "./userSearchList";
import Participants from "./Participants";
import toast from "react-hot-toast";
import { Session } from "next-auth";
import { useRouter } from "next/router";
interface ModalProps {
session: Session;
isOpen: boolean;
onClose: () => void;
}
const ConversationModal = ({ session, isOpen, onClose }: ModalProps) => {
const {
user: { id: userId },
} = session;
const router = useRouter();
const [username, setUsername] = useState("");
const [participants, setParticipants] = useState<Array<SearchedUser>>([]);
const [searchUsers, { data, loading, error }] = useLazyQuery<any>(
UserOperations.Queries.searchUsers
);
const [createConversation, { loading: createConversationLoading }] =
useMutation<CreateConversationData>(
ConversationOperations.Mutations.createConversation
);
console.log("HERE IS SEARCH DATA", data);
const onCreateConversation = async () => {
const participantIds = [userId, ...participants.map((p) => p.id)];
try {
// createConversation Mutation
const { data } = await createConversation({
variables: {
participantIds,
},
});
if (!data?.createConversation) {
throw new Error("Failed to create conversation");
}
const {
createConversation: { conversationId },
} = data;
router.push({ query: { conversationId } }); //대화방 주소
setParticipants([]);
setUsername("");
onClose();
console.log("DATA", data);
} catch (err: any) {
console.log("onCreateConversation", err);
toast.error(err?.message);
}
};
const onSearch = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
// search user query
searchUsers({ variables: { username } as any });
},
[username]
);
const addParticipant = (user: SearchedUser) => {
setParticipants((prev) => [...prev, user]);
setUsername("");
};
const removeParticipant = (userId: string) => {
console.log("removeParticipant");
setParticipants((prev) => prev.filter((p) => p.id !== userId));
};
return (
<>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent pb={4}>
<ModalHeader>대화 생성</ModalHeader>
<ModalCloseButton />
<ModalBody>
<form onSubmit={onSearch}>
<Stack spacing={4}>
<Input
placeholder="유저를 입력해주세요."
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<Button type="submit" disabled={!username} isLoading={loading}>
검색
</Button>
</Stack>
</form>
{data?.searchUsers && (
<UserSearchList
users={data?.searchUsers}
username={username}
addParticipant={addParticipant}
/>
)}
{participants.length !== 0 && (
<>
<Participants
participants={participants}
removeParticipant={removeParticipant}
/>
<Button
bg="blue.100"
width="100%"
mt={6}
_hover={{ bg: "gray.100" }}
isLoading={createConversationLoading}
onClick={onCreateConversation}
>
대화 생성
</Button>
</>
)}
</ModalBody>
</ModalContent>
</Modal>
</>
);
};
export default ConversationModal;
선택 누르시 대화 id가 생성이 됩니다
'Project' 카테고리의 다른 글
GraphQL 대화생성 Subscription (0) | 2023.08.29 |
---|---|
WebSocket/ Subscription (0) | 2023.08.29 |
GraphQL- 유저 검색 기능 (0) | 2023.08.23 |
Graphql Query, prisma (0) | 2023.08.22 |
Graphql 리액트 연결 (0) | 2023.08.22 |