[ 8부 ] GraphQL, Typescript 및 React로 Twitter 복제본 만들기( 트윗에 좋아요 추가 )
41799 단어 showdevnodecareerjavascript
참고로 저는 다음 챌린지를 시도합니다: Tweeter challenge
Db diagram
오늘 저는 트윗을 좋아할 가능성을 추가할 것을 제안합니다.
knex migrate:make create_likes_table -x ts
src/db/migrations/create_likes_table
import * as Knex from 'knex'
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('likes', (t) => {
t.increments('id')
t.integer('user_id').unsigned().notNullable()
t.integer('tweet_id').unsigned().notNullable()
t.unique(['user_id', 'tweet_id'])
t.foreign('user_id').references('id').inTable('users').onDelete('CASCADE')
t.foreign('tweet_id').references('id').inTable('tweets').onDelete('CASCADE')
})
}
export async function down(knex: Knex): Promise<void> {
return knex.raw('DROP TABLE likes CASCADE')
}
knex migrate:latest
트윗에 좋아요를 한 번만 표시할 수 있기 때문에 user_id 및 tweet_id 열과 관련된 고유 제약 조건을 추가합니다 ;).
src/resolvers/LikeResolver.ts
import { ApolloError } from 'apollo-server'
import {
Arg,
Authorized,
Ctx,
Int,
Mutation,
ObjectType,
Resolver,
} from 'type-graphql'
import { MyContext } from '../types/types'
@Resolver()
class LikeResolver {
@Mutation(() => String)
@Authorized()
async toggleLike(@Arg('tweet_id') tweet_id: number, @Ctx() ctx: MyContext) {
const { db, userId } = ctx
const [tweet] = await db('tweets').where('id', tweet_id)
if (!tweet) {
throw new ApolloError('Tweet not found')
}
const data = {
user_id: userId,
tweet_id: tweet_id,
}
try {
const [alreadyLiked] = await db('likes').where(data)
if (alreadyLiked) {
// Delete the like and return
await db('likes').where(data).del()
return 'Like deleted'
}
await db('likes').insert(data)
return 'Like added'
} catch (e) {
throw new ApolloError(e.message)
}
}
}
export default LikeResolver
"좋아요"추가 또는 제거를 관리하는 단일 메서드를 생성합니다. 그래서 나는 이미 "좋아요"가 있는지 확인하고 있다면 그것을 삭제합니다. 그렇지 않으면 추가합니다.
내 스키마 메서드에 LikeResolver도 추가해야 합니다.
src/server.ts
...
export const schema = async () => {
return await buildSchema({
resolvers: [AuthResolver, TweetResolver, LikeResolver],
authChecker: authChecker,
})
}
내 서버를 시작하면 모든 것이 제대로 작동합니다.
몇 가지 테스트를 작성해 보겠습니다.
src/tests/likes.test.ts
import db from '../db/connection'
import { generateToken } from '../utils/utils'
import { createLike, createTweet, createUser } from './helpers'
import { TOGGLE_LIKE } from './queries/likes.queries'
import { ADD_TWEET, FEED, DELETE_TWEET } from './queries/tweets.queries'
import { testClient } from './setup'
describe('Likes', () => {
beforeEach(async () => {
await db.migrate.rollback()
await db.migrate.latest()
})
afterEach(async () => {
await db.migrate.rollback()
})
it('should add a like', async () => {
const user = await createUser()
const tweet = await createTweet(user)
const { mutate } = await testClient({
req: {
headers: {
authorization: 'Bearer ' + generateToken(user),
},
},
})
const res = await mutate({
mutation: TOGGLE_LIKE,
variables: {
tweet_id: tweet.id,
},
})
const [like] = await db('likes').where({
user_id: user.id,
tweet_id: tweet.id,
})
expect(like).not.toBeUndefined()
expect(res.data.toggleLike).toEqual('Like added')
expect(res.errors).toBeUndefined()
})
it('should add delete a like', async () => {
const user = await createUser()
const tweet = await createTweet(user)
await createLike(user, tweet)
const { mutate } = await testClient({
req: {
headers: {
authorization: 'Bearer ' + generateToken(user),
},
},
})
const res = await mutate({
mutation: TOGGLE_LIKE,
variables: {
tweet_id: tweet.id,
},
})
const [deleted] = await db('likes').where({
user_id: user.id,
tweet_id: tweet.id,
})
expect(deleted).toBeUndefined()
expect(res.data.toggleLike).toEqual('Like deleted')
expect(res.errors).toBeUndefined()
})
it('should not authorized an anonymous user to like a tweet', async () => {
const user = await createUser()
const tweet = await createTweet(user)
const { mutate } = await testClient()
const res = await mutate({
mutation: TOGGLE_LIKE,
variables: {
tweet_id: tweet.id,
},
})
const likes = await db('likes')
expect(likes.length).toEqual(0)
expect(res.errors![0].message).toEqual('Unauthorized')
})
it('should not authorized an anonymous user to delete a like', async () => {
const user = await createUser()
const tweet = await createTweet(user)
const like = await createLike(user, tweet)
const { mutate } = await testClient()
const res = await mutate({
mutation: TOGGLE_LIKE,
variables: {
tweet_id: tweet.id,
},
})
const likes = await db('likes')
expect(likes.length).toEqual(1)
expect(res.errors![0].message).toEqual('Unauthorized')
})
})
완료하기 전에 트윗의 좋아요 수를 확인하는 것이 좋습니다. 임의의 좋아요를 추가하기 위해 seed 파일을 수정했습니다. ;)에서 확인하겠습니다.
src/entities/Tweet.ts
@Field()
likesCount: number
좋아요에 @FieldResolver를 추가할 때 n+1 문제를 피하기 위해 "dataloader"가 필요합니다.
src/dataloaders/dataloaders.ts
import DataLoader from 'dataloader'
import db from '../db/connection'
import User from '../entities/User'
export const dataloaders = {
userDataloader: new DataLoader<number, User, unknown>(async (ids) => {
const users = await db('users').whereIn('id', ids)
return ids.map((id) => users.find((u) => u.id === id))
}),
// Get the likesCount for each tweet
likesCountDataloader: new DataLoader<number, any, unknown>(async (ids) => {
const counts = await db('likes')
.whereIn('tweet_id', ids)
.count('tweet_id', { as: 'likesCount' })
.select('tweet_id')
.groupBy('tweet_id')
return ids.map((id) => counts.find((c) => c.tweet_id === id))
}),
}
이제 TweetResolver에 @FieldResolver를 추가할 수 있습니다.
src/resolvers/TweetResolver.ts
@FieldResolver(() => Int)
async likesCount(@Root() tweet: Tweet, @Ctx() ctx: MyContext) {
const {
dataloaders: { likesCountDataloader },
} = ctx
const count = await likesCountDataloader.load(tweet.id)
return count?.likesCount || 0
}
세 가지 SQL 쿼리가 있습니다. 하나는 트윗을 검색합니다. 연결된 사용자를 검색하는 또 다른 하나와 좋아요 수를 검색하는 마지막 항목.
그러나 문제가 있음을 알 수 있습니다. toggleLike 메서드를 여러 번 실행하고 피드 메서드를 새로고침하면 likesCount 속성이 업데이트되지 않는 것을 볼 수 있습니다. 이 문제를 방지하려면 "좋아요"를 추가하거나 제거할 때 캐시를 지워야 합니다.
데이터 로더 캐시 지우기
src/resolvers/LikeResolver.ts
@Mutation(() => String)
@Authorized()
async toggleLike(@Arg('tweet_id') tweet_id: number, @Ctx() ctx: MyContext) {
const {
db,
userId,
dataloaders: { likesCountDataloader }, // I get the dataloaders from the context
} = ctx
const [tweet] = await db('tweets').where('id', tweet_id)
if (!tweet) {
throw new ApolloError('Tweet not found')
}
const data = {
user_id: userId,
tweet_id: tweet_id,
}
try {
const [alreadyLiked] = await db('likes').where(data)
if (alreadyLiked) {
// Delete the like and return
await db('likes').where(data).del()
likesCountDataloader.clear(tweet_id) // I clear the dataloader for this particular tweet_id
return 'Like deleted'
}
await db('likes').insert(data)
likesCountDataloader.clear(tweet_id) // I clear the dataloader for this particular tweet_id
return 'Like added'
} catch (e) {
throw new ApolloError(e.message)
}
}
이제 예상대로 작동해야 합니다.
오늘은 여기까지입니다 ;).
안녕히 계세요! ;)
Reference
이 문제에 관하여([ 8부 ] GraphQL, Typescript 및 React로 Twitter 복제본 만들기( 트윗에 좋아요 추가 )), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ipscodingchallenge/part-8-creating-a-twitter-clone-with-graphql-typescript-and-react-add-likes-to-tweet-46p5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)