Skip to content

Commit

Permalink
[FEAT] Quiz, Video Favorite 관련 API 구현 (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
2tle authored Aug 23, 2024
1 parent d11c9b9 commit 59d01d2
Show file tree
Hide file tree
Showing 32 changed files with 1,128 additions and 59 deletions.
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

0 comments on commit 59d01d2

Please sign in to comment.