공감 기능과 서버의 interaction을 집중적으로 수정하였다.
문제는 대략 세가지였다.
client에서 API는 단일 type으로 요청받도록 수정했다.
before
useEffect(() => {
axios({
baseURL: API_HOST,
url: `/@${userId}/${urlSlug}`,
})
.then(response => {
const _post = response.data;
setPost(_post);
if (user.username !== '') {
// 서버 api 수정까지 연달아 요청(post.id 의존성)
// API 수정 예정: post + liked join (= SinglePost type)
axios({
baseURL: API_HOST,
url: `/${user.username}/liked/${_post.id}`,
})
.then(reponse => {
setLiked(reponse.data.length !== 0);
})
.catch(error => {
console.error(error);
});
}
})
.catch(error => {
console.error(error);
if (error.response.status === 404) {
navigate('/NotFound', { replace: true });
}
});
}, [navigate, urlSlug, user.username, userId]);
after
useEffect(() => {
axios
.get(`${API_HOST}/@${userId}/${urlSlug}/`, {
params: {
loginUserName: user.username,
},
})
.then(response => {
const _post = response.data;
setPost(_post);
setLiked(_post.liked);
})
.catch(error => {
console.error(error);
if (error.response.status === 404) {
navigate('/NotFound', { replace: true });
}
});
}, [navigate, urlSlug, user.username, userId]);
서버
서버의 /@:username/:url_slug/ api에서 기존 post query는 같고 아래 내용이 추가되었다. join을 세번하기엔 login user가 없을 수도 있어서 따로 조건을 체크하고 query를 한 번 더 작성하였다.
//postApi.ts
const loginUserName = req.query.loginUserName;
if (loginUserName?.length && typeof loginUserName === 'string') {
try {
post.liked = await isLikedPost(loginUserName, post.id);
} catch (err) {
post.liked = false;
}
}
// postLikeApi.ts
export async function isLikedPost(loginUserName: string, postId: string) {
try {
const likedResult = await dbConn.query(
`SELECT id FROM public."POST_LIKES" WHERE (fk_user_id='${loginUserName}' AND fk_post_id='${postId}');`
);
return likedResult.rows.length !== 0;
} catch (err) {
console.error(err);
throw err;
}
}
like 버튼을 누를 때 서버에 업데이트하는 작업을 로그인 기능 완료 후 하려했으나 태스크를 한 번에 완료하고 싶어 로그인 유저 값을 const로 변경해서 진행하였다.
before
like 버튼을 누를 때 단순히 local state를 변경하였다.
const handleLikeToggle = () => {
setLiked(!liked);
};
after
const handleLikeToggle = () => {
try {
const userName = user.username;
if (!userName) {
console.log('login 기능 보수 중^^, 필요하다면 const 값을 주세여');
return;
}
axios
.post(`${API_HOST}/${userName}/${liked ? 'unlike' : 'like'}/${post.id}/`)
.then(response => {
post.likes = response.data.likes;
setLiked(response.data.liked);
})
.catch(error => {
console.error(error);
});
} catch (error) {
console.error(error);
}
};
etag를 사용하는 로직을 추가하였었다.
etag(post.body)처럼 post 내용으로 etag hash를 서버에서 구하여 주었는데 서버 리셋 전의 캐시 데이터가 남아있어 cors 에러를 보았다.
그래서 다른 식별자가 필요해보여서 post.id랑 함께 etag hash 구할 때 주게 변경하였다.
// 사용자의 특정 포스팅 GET (by slug)
router.get('/@:username/:url_slug/', async (req, response) => {
const userName = req.params.username;
const slug = req.params.url_slug;
// 사용자 이름이 넘어오지 않은 경우
if (userName === undefined || userName === '') {
return response.status(201).json(`Unkown user ${userName}`);
}
try {
// 사용자 이름과 일치하는 포스팅 데이터 (post <-left join- user)
const result = await dbConn.query(
`SELECT p.id, p.title, p.released_at, p.body, p.short_description,
p.is_markdown, p.is_private, p.thumbnail, p.url_slug,
json_build_object(
'username', u.user_name,
'email', u.email_addr,
'is_certified', u.is_certified) as user,
p.likes, false as liked
FROM public."BLOG_POSTS" as p
LEFT JOIN public."BLOG_USERS" as u
ON p.fk_user_name=u.user_name
WHERE (p.fk_user_name='${userName}' AND p.url_slug='${slug}')`
);
if (result.rows.length === 0) {
return response.status(404).json({ err: 'Post Not Found' });
}
const post = result.rows[0];
let etagHashKey = '' + post.id + post.body + post.likes;
const loginUserName = req.query.loginUserName;
if (loginUserName?.length && typeof loginUserName === 'string') {
try {
post.liked = await isLikedPost(loginUserName, post.id);
} catch (err) {
post.liked = false;
}
etagHashKey += post.liked;
}
response.setHeader('Cache-Control', 'private, no-cache');
if (
req.headers['if-none-match'] === etag(etagHashKey) &&
req.headers['if-modified-since'] === new Date(post.released_at).toUTCString()
) {
return response.status(304).send();
}
response.setHeader('ETag', etag(etagHashKey));
response.setHeader('Last-Modified', new Date(post.released_at).toUTCString());
return response.status(200).json(post);
} catch (err) {
return response.status(404).json({ err: 'Post Not Found' });
}
});
최종적으로 released date, post id, post date, liked, likes로 cache를 판단하였다.
그러나 썸네일만 변경되는 케이스 등을 위해 json stringify와 이미지 id값을 추가하여야 할 것으로 보여, 이미지 사용 가능한 환경이 되면 캐쉬관련해서 추가 스터디를 해야할 것으로 보인다.