🍔 핵심 내용
🥑 comment기능을 만들어 보자.
🍔 코드 리뷰
🥑 schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
firstName String
lastName String?
userName String @unique
email String @unique
password String
bio String?
avatar String?
photos Photo[]
likes Like[]
followers User[] @relation("FollowRelation", references: [id])
followings User[] @relation("FollowRelation", references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
comments Comment[]
}
model Photo {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
file String
caption String?
hashtags Hashtag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
likes Like[]
comments Comment[]
}
model Hashtag {
id Int @id @default(autoincrement())
hashtag String @unique
photos Photo[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Like {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
photo Photo @relation(fields: [photoId], references: [id])
userId Int
photoId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, photoId])
}
model Comment {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
photo Photo @relation(fields: [photoId], references: [id])
payload String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int
photoId Int
}
스키마에 comment 모델 추가
🥑 comments.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type comments {
id: Int!
user: User!
photo: Photo!
payload: String!
isMine: Boolean!
createdAt: String!
updatedAt: String!
}
`;
내가 작성한 코멘트라면, 삭제나 수정을 할 수 있어야 하기 때문에 isMine을 추가 하였다. (photo에도 추가 함)
여기서 payload는 작성할 text다.
🥑 createComment.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type createCommentResult {
ok: Boolean!
error: String
}
type Mutation {
createComment(photoId: Int!, payload: String!): createCommentResult
}
`;
🥑 createComment.resolvers.js
import client from "../../client";
import { protectedResolver } from "../../users/users.utils";
export default {
Mutation: {
createComment: protectedResolver(
async (_, { photoId, payload }, { loggedInUser }) => {
const ok = await client.photo.findUnique({
where: {
id: photoId,
},
select: {
id: true,
},
});
if (!ok) {
return {
ok: false,
error: "사진을 찾을 수 없습니다.",
};
}
await client.comment.create({
data: {
payload,
photo: {
connect: {
id: photoId,
},
},
user: {
connect: {
id: loggedInUser.id,
},
},
},
});
return {
ok: true,
};
}
),
},
};
특이 사항은 없다. comment 모델에 인자로 받은 payload값을 넣고 photo와 user를 연결해주었다.
🍔 핵심 내용
🥑 seePhotoComments 기능을 만들어보자.
photo의 리턴값에서 comment도 확인 할 수 있도록 해보자.
🍔 코드 리뷰
🥑 seePhotoComments.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type Query {
seePhotoComments(id: Int!): [Comment]
}
`;
특정 photoId 값을 넣으면 해당 photo에 달려있는 comment 값이 출력 되도록 해보자.
🥑 seePhotoComments.resolvers.js
import client from "../../client";
//pagination으로도 가능
export default {
Query: {
seePhotoComments: (_, { id }) =>
client.comment.findMany({
where: {
photoId: id,
},
orderBy: "asc",
}),
},
};
//이건 전체 호출
// export default {
// Query: {
// seePhotoComments: (_, { id }) =>
// client.photo
// .findUnique({
// where: {
// id,
// },
// })
// .comment(),
// },
// };
다른 큰 특이사항은 없다.
🍔 핵심 내용
🥑 isMine 기능을 만들어 보자.
코멘트나 photo에서 삭제나 수정 기능을 사용하려면 본인이 작성한 것이어야만 한다. 이를 위해 이전 user의 isMe와 유사하게, isMine 기능을 만들어 보자.
🍔 코드 리뷰
🥑 photos.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type Photo {
id: Int!
user: User
file: String!
caption: String
likes: Int!
comments: Int!
isMine: Boolean! <--추가
hashtags: [Hashtag]
createdAt: String!
updatedAt: String!
}
type Hashtag {
id: Int!
createdAt: String!
updatedAt: String!
hashtag: String!
photos(page: Int!): [Photo] #일부 필드값에만 args 추가 가능함
totalPhotos: Int!
}
type Like {
id: Int!
photo: Photo!
createdAt: String!
updatedAt: String!
}
`;
isMine 추가
🥑 photos.resolvers.js
import client from "../client";
export default {
Photo: {
user: ({ userId }) => {
return client.user.findUnique({ where: { id: userId } });
},
hashtags: ({ id }) => {
return client.hashtag.findMany({ where: { photos: { some: { id } } } });
},
likes: ({ id }) => {
return client.like.count({ where: { photoId: id } });
},
comments: ({ id }) => {
return client.comment.count({ where: { photoId: id } });
},
isMine: ({ userId }, _, { loggedInUser }) => { <--추가
if (!loggedInUser) {
return false;
}
return userId === loggedInUser.id;
},
},
Hashtag: {
photos: ({ id }, { page }, { loggedInUser }) => {
console.log(page);
return client.hashtag.findUnique({ where: { id } }).photos();
}, //페이지네이션 기능 가능, 일부 필드값만 context값 쓸 수 있음
totalPhotos: ({ id }) =>
client.photo.count({
where: {
hashtags: {
some: {
id,
},
},
},
}),
},
};
resolver에 isMine 추가. loggedInUser 값이 null일수도 있기 때문에 if 구절을 사용하자.
위와 같은 작업을 똑같이 comment부분에도 추가해주면 된다.
🥑 comments.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type Comment {
id: Int!
user: User!
photo: Photo!
payload: String!
isMine: Boolean! <--추가
createdAt: String!
updatedAt: String!
}
`;
🥑 comments.resolvers.js
export default {
Comment: {
isMine: ({ userId }, _, { loggedInUser }) => {
if (!loggedInUser) {
return false;
}
return userId === loggedInUser.id;
},
},
};
추가 완료.
🍔 핵심 내용
🥑 deletePhoto와 deleteComment를 만들어 보자.
먼저 유저 본인이 작성한 사진이나 코멘트만 지울 수 있고, 여기서 유의점은 photo 삭제 시, 해당 photo에 연결 되어 있는 like와 comment를 먼저 매뉴얼로 지워주어야 한다. 코멘트는 그런 에러가 없는 것 같다. (아마도 코멘트는 photo에 종속적 성질이 있어서 그런 것 같다.)
🍔 코드 리뷰
🥑 deletePhoto.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type deletePhotoResult {
ok: Boolean!
error: String
}
type Mutation {
deletePhoto(id: Int!): deletePhotoResult!
}
`;
🥑 deletePhoto.resolvers.js
import client from "../../client";
import { protectedResolver } from "../../users/users.utils";
export default {
Mutation: {
deletePhoto: protectedResolver(async (_, { id }, { loggedInUser }) => {
const photo = await client.photo.findUnique({
where: {
id,
},
select: {
userId: true,
},
});
if (!photo) {
return {
ok: false,
error: "사진을 찾을 수 없습니다.",
};
} else if (photo.userId !== loggedInUser.id) {
return {
ok: false,
error: "권한이 없습니다.",
};
} else {
await client.like.deleteMany({
where: {
photoId: id,
},
});
await client.comment.deleteMany({
where: {
photoId: id,
},
});
await client.photo.delete({
where: {
id,
},
});
return {
ok: true,
};
}
}),
},
};
마지막 단계를 보면, 해당 photo의 like와 comment를 먼저 삭제해주고 그리고 마지막으로 해당 photo를 삭제해주는 로직이다.
🥑 deleteComment.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type deleteCommentResult {
ok: Boolean!
error: String
}
type Mutation {
deleteComment(id: Int!): deleteCommentResult!
}
`;
🥑 deleteComment.resolvers.js
import client from "../../client";
import { protectedResolver } from "../../users/users.utils";
export default {
Mutation: {
deleteComment: protectedResolver(async (_, { id }, { loggedInUser }) => {
const comment = await client.comment.findUnique({
where: {
id,
},
select: {
userId: true,
},
});
if (!comment) {
return {
ok: false,
error: "코멘트를 찾을 수 없습니다.",
};
} else if (comment.userId !== loggedInUser.id) {
return {
ok: false,
error: "권한이 없습니다.",
};
} else {
await client.comment.delete({
where: {
id,
},
});
return {
ok: true,
};
}
}),
},
};
deletePhoto 와 거의 똑같은 로직이다.
🍔 핵심 내용
🥑 Mutation의 결과 값(ok, error)은 똑같다. 코드 재사용성
재사용 코드로 하나 만들어주자
🥑 editComment를 만들어 보자.
크게 특이 사항 없다.
🍔 코드 리뷰
🥑 shared.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type MutationResponse {
ok: Boolean!
error: String
}
`;
Mutation의 결과 값은 이거 고정 이다. 따라서, 재활용성을 위해 하나 만들어 주자.
🥑 editComment.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type Mutation {
editComment(id: Int!, payload: String!): MutationResponse!
}
`;
🥑 editComment.resolvers.js
import client from "../../client";
import { protectedResolver } from "../../users/users.utils";
export default {
Mutation: {
editComment: protectedResolver(
async (_, { id, payload }, { loggedInUser }) => {
const comment = await client.comment.findUnique({
where: {
id,
},
select: {
userId: true,
},
});
if (!comment) {
return {
ok: false,
error: "코멘트를 찾을 수 없습니다.",
};
} else if (comment.userId !== loggedInUser.id) {
return {
ok: false,
error: "권한이 없습니다",
};
} else {
await client.comment.update({
where: {
id,
},
data: {
payload,
},
});
return {
ok: true,
};
}
}
),
},
};
delete 로직과 유사하다.
🍔 핵심 내용
🥑 protectedResolver 리팩토링
해당 함수에는 사실 문제가 하나 있었다. 대부분 Mutation값에 로그인이 필요한 부분에 쓰였고, 해당 결과 값은 ok와 error이다. 하지만, protectedResolver가 query에 한 곳에 쓰이고 있다. 그곳은 바로 seeFeed다.
먼저 아래 코드를 보고 확인 해보자.
🍔 코드 리뷰
🥑 seeFeed.typeDefs.js
import { gql } from "apollo-server-core";
export default gql`
type Query {
seeFeed: [Photo]
}
`;
🥑 seeFeed.resolvers.js
import client from "../../client";
import { protectedResolver } from "../../users/users.utils";
export default {
Query: {
seeFeed: protectedResolver(async (_, __, { loggedInUser }) =>
client.photo.findMany({
where: {
OR: [
{
user: {
followers: {
some: {
id: loggedInUser.id,
},
},
},
},
{
userId: loggedInUser.id,
},
],
},
orderBy: {
createdAt: "desc",
},
})
),
},
};
이렇게 query 구문에서 쓰이고 있는데, 결과값은 Photo배열 값이다.
🥑 users.utils.js
export const protectedResolver = (ourResolver) => (
root,
args,
context,
info
) => {
if (!context.loggedInUser) {
const query = info.operation.operation === "query";
if (query) {
return null;
} else {
return {
ok: false,
error: "로그인이 필요합니다. 로그인 해주세요.",
};
}
}
return ourResolver(root, args, context, info);
};
info 안에는 많은 내용들이 들어가 있다. info.operation.operation 값에는 query가 들어가 있기 때문에 해당 결과 값을 null로 해놓으면 query에서 쓰이는 protectedResolver 결과값은 null로 에러가 생기지 않는다.
다시 말해 원래 ok: false 와 error: "로그인이 필요합니다. 로그인 해주세요." 를 뱉어야 하는데, query의 결과 값은 ok와 error가 아니기 때문에 에러가 생기는 거였는데, 이를 null값으로 처리해줌으로써 에러를 잡은 것이다.
'코딩강의 > 인스타그램클론(expo-노마드코더)' 카테고리의 다른 글
Direct Messages(1) (0) | 2021.04.07 |
---|---|
Photos Module(3) - AWS upload 세팅 (0) | 2021.04.07 |
Photos Module(1) (0) | 2021.03.31 |
User module(3) (0) | 2021.03.27 |
User module(2) (0) | 2021.03.20 |