Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hot fix/v1.0.4 좋아요 로직 관련 문제 수정 #36

Merged
merged 4 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ private List<Long> getMyExamIds(Pageable pageable, Long memberId, ExamStatus sta

@Override
public Page<SubmittedExamSummaryDto> findSubmittedExamSummaries(Pageable pageable, Long memberId) {
JPAQuery<Long> countQuery = queryFactory.select(exam.count())
JPAQuery<Long> countQuery = queryFactory.select(exam.countDistinct())
.from(exam)
.join(submission).on(exam.id.eq(submission.examId))
.where(submission.memberId.eq(memberId));
Expand All @@ -184,7 +184,7 @@ public Page<SubmittedExamSummaryDto> findSubmittedExamSummaries(Pageable pageabl
.from(exam)
.leftJoin(member).on(exam.memberId.eq(member.id))
.join(submission).on(exam.id.eq(submission.examId))
.where(exam.id.in(examIds))
.where(exam.id.in(examIds).and(submission.memberId.eq(memberId)))
.groupBy(
exam.id,
exam.title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import com.fluffy.auth.domain.MemberRepository;
import com.fluffy.exam.domain.dto.ExamSummaryDto;
import com.fluffy.exam.domain.dto.MyExamSummaryDto;
import com.fluffy.exam.domain.dto.SubmittedExamSummaryDto;
import com.fluffy.submission.domain.Answer;
import com.fluffy.submission.domain.Submission;
import com.fluffy.submission.domain.SubmissionRepository;
import com.fluffy.support.AbstractIntegrationTest;
import com.fluffy.support.data.MemberTestData;
import java.util.List;
Expand All @@ -24,6 +28,9 @@ class ExamRepositoryTest extends AbstractIntegrationTest {
@Autowired
private MemberRepository memberRepository;

@Autowired
private SubmissionRepository submissionRepository;

@Test
@DisplayName("출제된 시험 요약 목록을 조회할 수 있다.")
void findPublishedExamSummaries() {
Expand Down Expand Up @@ -133,4 +140,76 @@ void findMyExamSummaries() {
.containsExactlyElementsOf(List.of(3L, 1L))
);
}

@Test
@DisplayName("내가 제출한 시험 요약 목록을 조회할 수 있다.")
void findSubmittedExamSummaries() {
// given
Member member1 = MemberTestData.defaultMember().build();
memberRepository.save(member1);

Member member2 = MemberTestData.defaultMember().build();
memberRepository.save(member2);

Exam publishedExam1 = Exam.create("publishedExam1", member1.getId());
publishedExam1.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1")));
publishedExam1.publish();
examRepository.save(publishedExam1);

Exam publishedExam2 = Exam.create("publishedExam2", member1.getId());
publishedExam2.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1")));
publishedExam2.publish();
examRepository.save(publishedExam2);

submissionRepository.save(new Submission(
publishedExam1.getId(),
member1.getId(),
List.of(Answer.textAnswer(1L, "답1"))
));

submissionRepository.save(new Submission(
publishedExam2.getId(),
member1.getId(),
List.of(Answer.textAnswer(1L, "답2"))
));

submissionRepository.save(new Submission(
publishedExam1.getId(),
member2.getId(),
List.of(Answer.textAnswer(1L, "답3"))
));

submissionRepository.save(new Submission(
publishedExam1.getId(),
member1.getId(),
List.of(Answer.textAnswer(1L, "답1"))
));

// when
PageRequest pageable = PageRequest.of(0, 2);
Page<SubmittedExamSummaryDto> submittedExamSummaries = examRepository.findSubmittedExamSummaries(
pageable,
member1.getId()
);

System.out.println(submittedExamSummaries.getContent());
System.out.println(submittedExamSummaries.getTotalElements());
System.out.println(submittedExamSummaries.getTotalPages());
System.out.println(submittedExamSummaries.getNumber());
System.out.println(submittedExamSummaries.getSize());

// then
assertAll(
() -> assertThat(submittedExamSummaries.getTotalElements()).isEqualTo(2),
() -> assertThat(submittedExamSummaries.getTotalPages()).isEqualTo(1),
() -> assertThat(submittedExamSummaries.getContent()
.stream()
.map(SubmittedExamSummaryDto::getSubmissionCount)
).containsExactlyElementsOf(List.of(2L, 1L)),
() -> assertThat(submittedExamSummaries.getContent()
.stream()
.map(SubmittedExamSummaryDto::getTitle)
).containsExactlyElementsOf(List.of("publishedExam1", "publishedExam2"))
);
}
}
13 changes: 5 additions & 8 deletions web/src/api/examAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,14 @@ export const ExamAPI = {
return data;
},

like: async (examId: number, controller?: AbortController) => {
const { data } = await apiV1Client.post<void>(`/exams/${examId}/like`, {
signal: controller?.signal,
});
like: async (examId: number) => {
const { data } = await apiV1Client.post<void>(`/exams/${examId}/like`);

return data;
},

unlike: async (examId: number, controller?: AbortController) => {
const { data } = await apiV1Client.delete<void>(`/exams/${examId}/like`, {
signal: controller?.signal,
});
unlike: async (examId: number) => {
const { data } = await apiV1Client.delete<void>(`/exams/${examId}/like`);
return data;
},
};
Expand Down
17 changes: 0 additions & 17 deletions web/src/components/layouts/base/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,6 @@ const Footer = () => {
@alstn113
</a>
</div>
<div>
service:
<a
href="https://github.com/alstn113/fluffy"
target="_blank"
rel="noopener noreferrer"
className="font-semibold hover:underline ml-1"
>
@fluffy
</a>
</div>
<div>
email:
<a href="mailto:[email protected]" className="font-semibold hover:underline ml-1">
[email protected]
</a>
</div>
</div>
</div>
</div>
Expand Down
108 changes: 44 additions & 64 deletions web/src/hooks/api/exam/useExamLikeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import toast from 'react-hot-toast';
import useGetExamDetailSummary from '@/hooks/api/exam/useGetExamDetailSummary.ts';
import { ExamAPI, ExamDetailSummaryResponse } from '@/api/examAPI';

interface useExamLikeManagerProps {
interface UseExamLikeManagerProps {
examId: number;
initialIsLiked: boolean;
initialLikeCount: number;
Expand All @@ -15,7 +15,7 @@ const useExamLikeManager = ({
examId,
initialIsLiked,
initialLikeCount,
}: useExamLikeManagerProps) => {
}: UseExamLikeManagerProps) => {
const queryClient = useQueryClient();
const user = useUser();

Expand All @@ -24,87 +24,67 @@ const useExamLikeManager = ({

const debounceTimeout = useRef<number | null>(null);

const invalidateQueryDebounced = () => {
const debounceInvalidateQueries = () => {
if (debounceTimeout.current) {
clearTimeout(debounceTimeout.current);
}
debounceTimeout.current = setTimeout(async () => {
await queryClient.invalidateQueries({
debounceTimeout.current = setTimeout(() => {
queryClient.invalidateQueries({
queryKey: useGetExamDetailSummary.getKey(examId),
refetchType: 'all',
});
}, 300);
};

const { mutate: likeExam } = useMutation({
mutationFn: ExamAPI.like,
onMutate: async () => {
await queryClient.cancelQueries({
queryKey: useGetExamDetailSummary.getKey(examId),
});

const prevData = queryClient.getQueryData<ExamDetailSummaryResponse>(
useGetExamDetailSummary.getKey(examId),
);

setIsLiked(true);
setLikeCount(likeCount + 1);

return prevData;
},
onError: (_error, _variables, context) => {
if (context) {
queryClient.setQueryData(useGetExamDetailSummary.getKey(examId), context);
}

setIsLiked(false);
setLikeCount(likeCount - 1);
},
onSettled: async () => {
invalidateQueryDebounced();
},
});

const { mutate: unlikeExam } = useMutation({
mutationFn: ExamAPI.unlike,
onMutate: async () => {
await queryClient.cancelQueries({
queryKey: useGetExamDetailSummary.getKey(examId),
});

const prevData = queryClient.getQueryData<ExamDetailSummaryResponse>(
useGetExamDetailSummary.getKey(examId),
);

setIsLiked(false);
setLikeCount(likeCount - 1);

return prevData;
},
onError: (_error, _variables, context) => {
if (context) {
queryClient.setQueryData(useGetExamDetailSummary.getKey(examId), context);
}
const useLikeMutation = (
mutationFunction: (examId: number) => Promise<void>,
isLikeAction: boolean,
) => {
return useMutation({
mutationFn: mutationFunction,
onMutate: async () => {
await queryClient.cancelQueries({
queryKey: useGetExamDetailSummary.getKey(examId),
});

setIsLiked(isLikeAction);
setLikeCount((prevCount) => prevCount + (isLikeAction ? 1 : -1));

const previousData = queryClient.getQueryData<ExamDetailSummaryResponse>(
useGetExamDetailSummary.getKey(examId),
);

return { previousData };
},
onError: (_error, _variables, context) => {
if (context) {
queryClient.setQueryData(useGetExamDetailSummary.getKey(examId), context.previousData);
}

toast.error(`좋아요${isLikeAction ? '에' : ' 취소에'} 실패했습니다.`);

setIsLiked(!isLikeAction);
setLikeCount((prevCount) => prevCount - (isLikeAction ? 1 : -1));
},
onSettled: debounceInvalidateQueries,
});
};

setIsLiked(true);
setLikeCount(likeCount + 1);
},
onSettled: async () => {
invalidateQueryDebounced();
},
});
const { mutate: like } = useLikeMutation(ExamAPI.like, true);
const { mutate: unlike } = useLikeMutation(ExamAPI.unlike, false);

const toggleLike = () => {
if (!user) {
toast.error('로그인이 필요합니다.');
toast.error('좋아요를 누르려면 로그인이 필요합니다.');
return;
}

if (isLiked) {
unlikeExam(examId);
unlike(examId);
return;
}

likeExam(examId);
like(examId);
};

return {
Expand Down
Loading