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

[FEAT] Quiz, Video Favorite 관련 API 구현 #74

Merged
merged 45 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9a353f4
feat: impl get and submit quiz
2tle Aug 10, 2024
128efb9
feat: add quiz controller test file
2tle Aug 10, 2024
60061b4
refactor: change talk quiz api controller
2tle Aug 12, 2024
2f36184
fix: change quiz uri
2tle Aug 12, 2024
4400b39
feat: impl talk-quiz controller test
2tle Aug 12, 2024
d52d407
chore: remove unused quiz controller
2tle Aug 12, 2024
0f1abb5
feat: update talk.adoc
2tle Aug 12, 2024
c75eef4
fix: fix typo in FavoriteVideo.java
2tle Aug 12, 2024
726b3ef
feat: impl favorite func in talk and jobinterview service
2tle Aug 12, 2024
856071b
refactor: FavoriteVideo 별개의 서비스로 분리
2tle Aug 12, 2024
a6d47b4
feat: add favorite api route in controllers
2tle Aug 12, 2024
6640943
feat: add favorite testcode in talk and jobInterview Controller test
2tle Aug 12, 2024
3c9db0b
chore: update talk and jobInterview adoc
2tle Aug 12, 2024
f84f72c
fix: add eventperiod checker when submit quiz
2tle Aug 12, 2024
d97855d
feat: impl getQuizResult in service and repository
2tle Aug 12, 2024
ecfb3c8
feat: add quizController
2tle Aug 12, 2024
45c4b7f
feat: impl quiz controller test
2tle Aug 12, 2024
e26d2df
feat: add quiz.adoc
2tle Aug 12, 2024
02c92f2
feat: add relation mapping method in user entity
2tle Aug 12, 2024
391c2cd
fix: user 연관관계 버그 수정
2tle Aug 12, 2024
5fa510e
chore: remove adoc comments
2tle Aug 16, 2024
e439f1e
chore: remove divider
2tle Aug 16, 2024
23295ef
chore: add double quotes
2tle Aug 16, 2024
e719788
feat: 퀴즈 검색 로직 세분화
2tle Aug 16, 2024
a0a92df
fix: merge from develop
2tle Aug 16, 2024
a119063
fix: remove video.EventPeriodRepository
2tle Aug 16, 2024
b9ba560
fix: prohibit to submit quiz when current year does not match talk year
2tle Aug 16, 2024
3e7cc3e
feat: add getUserQuiz
2tle Aug 16, 2024
e1ace47
refactor: 잡페어 인터뷰 레포지토리에서 가져오는 타입 변경
2tle Aug 17, 2024
7df007d
feat: add optional accessType and update authuser resolver
2tle Aug 17, 2024
3664ebe
chore: remove unnessary else-if
2tle Aug 17, 2024
986cf8e
feat: update favorite fields in JobInterview
2tle Aug 19, 2024
412a65f
feat: add access token in jobinterview test
2tle Aug 19, 2024
3a12f33
feat: add favorite field in talk
2tle Aug 19, 2024
6ffb596
feat: add getUserQuiz api test and docs
2tle Aug 19, 2024
d7a2174
chore: update application-local.yml to merge develop
2tle Aug 19, 2024
4078a06
refactor: add optional access token
2tle Aug 20, 2024
8d54b02
chore: change api name
2tle Aug 20, 2024
1a23f7d
feat: change response data when userquiz is null
2tle Aug 20, 2024
d435708
feat: add max quiz tryCount
2tle Aug 20, 2024
b68538b
fix: 유저 중복 삭제 제거
2tle Aug 21, 2024
51de2fa
Merge pull request #63 from SystemConsultantGroup/feat/47-quiz-video-…
2tle Aug 23, 2024
0f3d4e1
Merge pull request #70 from SystemConsultantGroup/feat/47-quiz-video-…
2tle Aug 23, 2024
ddc4cf0
fix: merge from develop
2tle Aug 23, 2024
2578972
Merge pull request #73 from SystemConsultantGroup/feat/47-quiz-video-…
2tle Aug 23, 2024
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
3 changes: 2 additions & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ include::{snippets}/exception-code-controller-test/get-exception-codes/exception
include::auth-controller-test.adoc[]
include::jobInterview.adoc[]
include::talk.adoc[]
include::quiz.adoc[]
include::notice.adoc[]
include::eventNotice.adoc[]
include::eventPeriod.adoc[]
include::gallery.adoc[]
include::application.adoc[]
include::application.adoc[]
14 changes: 10 additions & 4 deletions src/docs/asciidoc/jobInterview.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,32 @@
operation::job-interview-controller-test/create-job-interview[snippets="http-request,request-fields,http-response,response-fields"]
====

---
=== 잡페어 인터뷰 리스트 조회 (GET /jobInterviews)
====
operation::job-interview-controller-test/get-job-interview-list[snippets="http-request,query-parameters,http-response,response-fields"]
====

---
=== 잡페어 인터뷰 단건 조회 (GET /jobInterviews/{jobInterviewId})
====
operation::job-interview-controller-test/get-job-interview[snippets="http-request,path-parameters,http-response,response-fields"]
====

---
=== 잡페어 인터뷰 수정 (PUT /jobInterviews/{jobInterviewId})
====
operation::job-interview-controller-test/update-job-interview[snippets="http-request,path-parameters,request-fields,http-response,response-fields"]
====

---
=== 잡페어 인터뷰 삭제 (DELETE /jobInterviews/{jobInterviewId})
====
operation::job-interview-controller-test/delete-job-interview[snippets="http-request,path-parameters,http-response"]
====

=== 잡페어 인터뷰 관심 등록 (POST /jobInterviews/{jobInterviews}/favorite)
====
operation::job-interview-controller-test/create-job-interview-favorite[snippets="http-request,path-parameters,http-response"]
====

=== 잡페어 인터뷰 관심 삭제 (DELETE /jobInterviews/{jobInterviews}/favorite)
====
operation::job-interview-controller-test/delete-job-interview-favorite[snippets="http-request,path-parameters,http-response"]
====
8 changes: 8 additions & 0 deletions src/docs/asciidoc/quiz.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
== 퀴즈 결과 API
:source-highlighter: highlightjs

---
=== 퀴즈 결과 조회 (GET /quizzes/result)
====
operation::quiz-controller-test/get-quiz-results[snippets="http-request,query-parameters,http-response,response-fields"]
====
29 changes: 25 additions & 4 deletions src/docs/asciidoc/talk.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,47 @@
operation::talk-controller-test/create-talk[snippets="http-request,request-fields,http-response,response-fields"]
====

---
=== 대담 영상 리스트 조회 (GET /talks)
====
operation::talk-controller-test/get-talk-list[snippets="http-request,query-parameters,http-response,response-fields"]
====

---
=== 대담 영상 단건 조회 (GET /talks/{talkId})
====
operation::talk-controller-test/get-talk[snippets="http-request,path-parameters,http-response,response-fields"]
====

---
=== 대담 영상 수정 (PUT /talks/{talkId})
====
operation::talk-controller-test/update-talk[snippets="http-request,path-parameters,request-fields,http-response,response-fields"]
====

---
=== 대담 영상 삭제 (DELETE /talks/{talkId})
====
operation::talk-controller-test/delete-talk[snippets="http-request,path-parameters,http-response"]
====

=== 대담 영상의 퀴즈 조회 (GET /talks/{talkId}/quiz)
====
operation::talk-controller-test/get-quiz[snippets="http-request,path-parameters,http-response,response-fields"]
====

=== 대담 영상의 퀴즈 결과 제출 (POST /talks/{talkId}/quiz)
====
operation::talk-controller-test/submit-quiz[snippets="http-request,path-parameters,request-fields,http-response,response-fields"]
====

=== 대담 영상의 관심 등록 (POST /talks/{talkId}/favorite)
====
operation::talk-controller-test/create-talk-favorite[snippets="http-request,path-parameters,http-response"]
====

=== 대담 영상의 관심 삭제 (DELETE /talks/{talkId}/favorite)
====
operation::talk-controller-test/delete-talk-favorite[snippets="http-request,path-parameters,http-response"]
====

=== 유저의 퀴즈 제출 기록 조회 (GET /talks/{talkId/quiz/submit)
====
operation::talk-controller-test/get-user-quiz[snippets="http-request,path-parameters,http-response,response-fields"]
====
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
if (request == null) {
throw new InvalidJwtException(ExceptionCode.FAILED_TO_VALIDATE_TOKEN);
}

AccessType[] allowedTypes = Objects.requireNonNull(parameter.getParameterAnnotation(AuthUser.class)).accessType();
List<AccessType> accessTypeList = Arrays.asList(allowedTypes);

if(accessTypeList.contains(AccessType.OPTIONAL) && !hasAccessToken(request)) {
return null;
}

String contextPath = request.getRequestURI();
String refreshToken = extractRefreshToken(request);
String accessToken = extractAccessToken(request);
Expand All @@ -62,10 +70,9 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
throw new BadRequestException(ExceptionCode.REGISTER_NOT_FINISHED);
}
}
AccessType[] allowedTypes = Objects.requireNonNull(parameter.getParameterAnnotation(AuthUser.class)).accessType();
List<AccessType> accessTypeList = Arrays.asList(allowedTypes);

if (accessTypeList.contains(AccessType.ALL)) {

if (accessTypeList.contains(AccessType.ALL) || accessTypeList.contains(AccessType.OPTIONAL)) {
return extractedUser;
}
else if (extractedUserType.equals(UserType.ADMIN)) {
Expand Down Expand Up @@ -119,4 +126,10 @@ private User extractUser(String accessToken) {
return userRepository.findById(userId)
.orElseThrow(() -> new BadRequestException(ExceptionCode.NOT_FOUND_USER_ID));
}

private boolean hasAccessToken(HttpServletRequest request) {
final String BEARER = "Bearer ";
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
return authHeader != null && authHeader.startsWith(BEARER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ public enum ExceptionCode {
ID_NOT_FOUND(8200,"해당 ID에 해당하는 잡페어 인터뷰가 없습니다."),
TALK_ID_NOT_FOUND(8400, "해당 ID에 해당하는 대담 영상이 없습니다."),
NO_QUIZ(8401, "퀴즈 데이터가 존재하지 않습니다."),
NOT_FOUND_USER_QUIZ(8402, "퀴즈 제출 데이터가 존재하지 않습니다."),
ALREADY_QUIZ_SUCCESS(8601, "이미 퀴즈의 정답을 모두 맞추었습니다."),
ALREADY_FAVORITE(8801, "이미 관심 리스트에 추가되었습니다."),
NOT_FAVORITE(8802, "이미 관심 리스트에 추가되어 있지 않습니다."),
NOT_EVENT_PERIOD(8804, "퀴즈 이벤트 참여 기간이 아닙니다."),
MISMATCH_CURRENT_YEAR(8805, "대담 영상과 현재 이벤트 참여 연도가 일치하지 않습니다."),
TOO_MANY_TRY_QUIZ(8901, "퀴즈 최대 시도 횟수를 초과하였습니다."),

// file domain
NOT_FOUND_FILE_ID(5001, "요청한 ID에 해당하는 파일이 존재하지 않습니다."),
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/scg/stop/user/domain/AccessType.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public enum AccessType {
PROFESSOR,
STUDENT,
COMPANY,
ALL
ALL,
OPTIONAL
}
15 changes: 6 additions & 9 deletions src/main/java/com/scg/stop/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,8 @@
import com.scg.stop.video.domain.FavoriteVideo;
import com.scg.stop.video.domain.UserQuiz;
import com.scg.stop.global.domain.BaseTimeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.*;

import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
Expand Down Expand Up @@ -61,10 +56,10 @@ public class User extends BaseTimeEntity {
@OneToMany(fetch = LAZY, mappedBy = "user")
private List<Proposal> proposals = new ArrayList<>();

@OneToMany(fetch = LAZY, mappedBy = "user")
@OneToMany(fetch = LAZY, mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<UserQuiz> userQuizzes = new ArrayList<>();

@OneToMany(fetch = LAZY, mappedBy = "user")
@OneToMany(fetch = LAZY, mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<FavoriteVideo> favoriteVideos = new ArrayList<>();

@OneToMany(fetch = LAZY, mappedBy = "user")
Expand Down Expand Up @@ -100,4 +95,6 @@ public void register(String name, String email, String phone, UserType userType,
this.userType = userType;
this.signupSource = signupSource;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import com.scg.stop.video.domain.JobInterviewCategory;
import com.scg.stop.video.dto.request.JobInterviewRequest;
import com.scg.stop.video.dto.response.JobInterviewResponse;
import com.scg.stop.video.dto.response.JobInterviewUserResponse;
import com.scg.stop.video.service.FavoriteVideoService;
import com.scg.stop.video.service.JobInterviewService;
import com.scg.stop.user.domain.AccessType;
import com.scg.stop.user.domain.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -21,7 +25,7 @@
@RequestMapping("/jobInterviews")
public class JobInterviewController {
private final JobInterviewService jobInterviewService;

private final FavoriteVideoService favoriteVideoService;

@PostMapping
public ResponseEntity<JobInterviewResponse> createJobInterview(
Expand All @@ -33,19 +37,22 @@ public ResponseEntity<JobInterviewResponse> createJobInterview(
}

@GetMapping
public ResponseEntity<Page<JobInterviewResponse>> getJobInterviews(
public ResponseEntity<Page<JobInterviewUserResponse>> getJobInterviews(
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "year", required = false) Integer year,
@RequestParam(value = "category", required = false) JobInterviewCategory category,
@AuthUser(accessType = {AccessType.OPTIONAL}) User user,
@PageableDefault(page = 0, size = 10) Pageable pageable
) {
Page<JobInterviewResponse> interviews = jobInterviewService.getJobInterviews(year, category, title, pageable);
return ResponseEntity.status(HttpStatus.OK).body(interviews);
return ResponseEntity.status(HttpStatus.OK).body(jobInterviewService.getJobInterviews(user, year, category, title, pageable));
}

@GetMapping("/{jobInterviewId}")
public ResponseEntity<JobInterviewResponse> getJobInterview(@PathVariable("jobInterviewId") Long jobInterviewId) {
return ResponseEntity.status(HttpStatus.OK).body(jobInterviewService.getJobInterview(jobInterviewId));
public ResponseEntity<JobInterviewUserResponse> getJobInterview(
@PathVariable("jobInterviewId") Long jobInterviewId,
@AuthUser(accessType = {AccessType.OPTIONAL}) User user
) {
return ResponseEntity.status(HttpStatus.OK).body(jobInterviewService.getJobInterview(jobInterviewId, user));
}

@PutMapping("/{jobInterviewId}")
Expand All @@ -66,7 +73,23 @@ public ResponseEntity<Void> deleteJobInterview(
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

@PostMapping("/{jobInterviewId}/favorite")
public ResponseEntity<Void> createJobInterviewFavorite(
@PathVariable("jobInterviewId") Long jobInterviewId,
@AuthUser(accessType = {AccessType.ALL}) User user
) {
favoriteVideoService.createJobInterviewFavorite(jobInterviewId, user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}

@DeleteMapping("/{jobInterviewId}/favorite")
public ResponseEntity<Void> deleteJobInterviewFavorite(
@PathVariable("jobInterviewId") Long jobInterviewId,
@AuthUser(accessType = {AccessType.ALL}) User user
) {
favoriteVideoService.deleteJobInterviewFavorite(jobInterviewId, user);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}


}
35 changes: 35 additions & 0 deletions src/main/java/com/scg/stop/video/controller/QuizController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.scg.stop.video.controller;

import com.scg.stop.auth.annotation.AuthUser;
import com.scg.stop.user.domain.AccessType;
import com.scg.stop.user.domain.User;
import com.scg.stop.video.dto.response.UserQuizResultResponse;
import com.scg.stop.video.service.QuizService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequiredArgsConstructor
@RequestMapping("/quizzes")
public class QuizController {
private final QuizService quizService;

@GetMapping("/result")
public ResponseEntity<Page<UserQuizResultResponse>> getQuizResults(
@RequestParam(value = "year", required = false) Integer year,
@AuthUser(accessType = {AccessType.ADMIN}) User user,
@PageableDefault(page = 0, size = 10) Pageable pageable
) {
Page<UserQuizResultResponse> responses = quizService.getQuizResults(year, pageable);
return ResponseEntity.status(HttpStatus.OK).body(responses);
}
}
Loading
Loading