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

댓글 기능 추가 #51

Merged
merged 29 commits into from
Nov 19, 2024
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7ef64dd
feat: 로그인이 되지 않을시 발생하는 NotLoginException 추가
starwook Nov 17, 2024
29af3be
feat: NotLoginException을 잡는 GlobalExceptionHandler 추가
starwook Nov 17, 2024
7571e72
feat: 댓글 추가 기능 구현
starwook Nov 17, 2024
aa0d737
feat: 댓글 조회시 삭제 가능 여부 표시
starwook Nov 18, 2024
7bae8e6
feat: 댓글 조회시 IfAuthor로 본인이 작성한 댓글 여부를 변수로 포함하도록 적용
starwook Nov 18, 2024
4b17e32
refactor: 자바 모듈에서 엔티티를 반환시 Nullable 표시를 하여 코틀린에서 충분히 이를 인식하도록 적용
starwook Nov 18, 2024
b0bf008
refactor: Question 테이블 전략을 조인 전랴으로 수정
starwook Nov 18, 2024
db7ba0e
feat: 댓글 생성시 문제 정보 또한 포함하도록 수정
starwook Nov 18, 2024
47a267d
feat: 문제에 해당하는 댓글 조회 기능 추가
starwook Nov 18, 2024
98d2a41
refactor: ID를 조회하고 문제와 유저정보를 가져오는 부분을 메소드로 분리
starwook Nov 18, 2024
3d6e2d9
feat: View - 댓글 표시 모달 구현
starwook Nov 18, 2024
6c985e1
feat: View - BootStrap 의존 추가
starwook Nov 18, 2024
2c7aa2d
feat: 선택지를 제외하고 Question의 정보만을 담아서 반환하는 ResponseQuestionDto 구현
starwook Nov 18, 2024
f974362
feat: View - 댓글 창에서 문제 본문의 내용을 볼 수 있도록 구현
starwook Nov 18, 2024
5c9d975
feat: View - 댓글 페이지 문제 본문에 줄바꿈 적용
starwook Nov 18, 2024
1dd7c4b
feat: Application 모듈에 kotlin.spring 의존성 추가
starwook Nov 18, 2024
839e3f0
refactor: 댓글 조회시 로그인 상태를 먼저 확인하도록 수정
starwook Nov 18, 2024
d2af82b
feat: 댓글 삭제 기능 구현
starwook Nov 18, 2024
50ddc11
feat: View - 댓글 삭제 기능 구현
starwook Nov 18, 2024
ceaca29
refactor: 댓글 삭제 및 등록시, 모달 페이지가 그대로 유지되도록 수정
starwook Nov 18, 2024
7283a59
feat: 객관식 문제일 경우 Application모듈에 반환 전에 지연로딩인 선택지를 초기화하도록 구현
starwook Nov 18, 2024
aadd0a3
refactor: View - 업데이트 페이지 Attribute 속성 이름 수정
starwook Nov 18, 2024
0f266fa
refactor: View - 해설 보기 버튼 클릭시 문제 상세창이 뜨도록 수정
starwook Nov 18, 2024
3671f7e
refactor: View - 문제화면 해설창 UI 비중 수정
starwook Nov 18, 2024
4e7b009
feat: View - 이미지 좌우 스크롤 기능 구현
starwook Nov 18, 2024
4889bc4
feat: View - 문제 상세 페이지 정답에 따른 색깔 표현 구현
starwook Nov 18, 2024
a786745
feat: View - 문제 상세 페이지 해설 추가
starwook Nov 18, 2024
226fa29
style: 코드 컨벤션 적용
starwook Nov 18, 2024
c9f45a4
test: 전공 문제 페이지 URL 수정
starwook Nov 18, 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
2 changes: 2 additions & 0 deletions application/build.gradle
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ plugins {
id 'io.freefair.lombok' version '8.10'
id 'org.jetbrains.kotlin.plugin.lombok' version '1.8.0'

//임시 스프링 프록시 기능을 사용하기 위함( all-open)
id "org.jetbrains.kotlin.plugin.spring" version "1.4.32"
/*
API 명세
*/
Original file line number Diff line number Diff line change
@@ -10,5 +10,5 @@
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AddLoginStatusAttribute {
public @interface AddLoginStatusAttributeToView {
}
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ public class LoginAspect {
* 메서드 실행 전후에 이 advice가 실행됩니다.
* Model에 login 상태 부여
*/
@Around("@annotation(com.comssa.api.login.aspect.AddLoginStatusAttribute)")
@Around("@annotation(com.comssa.api.login.aspect.AddLoginStatusAttributeToView)")
public Object addLoginStatusAttribute(ProceedingJoinPoint joinPoint) throws Throwable {
// 현재 메서드의 파라미터 가져오기
Object[] args = joinPoint.getArgs();
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.comssa.api.question.common.controller;

import com.comssa.api.login.aspect.AddLoginStatusAttribute;
import com.comssa.api.login.aspect.AddLoginStatusAttributeToView;
import com.comssa.api.question.common.service.QuestionSelectorService;
import com.comssa.persistence.question.license.domain.LicenseCategory;
import com.comssa.persistence.question.license.dto.response.ResponseLicenseSessionDto;
@@ -26,7 +26,7 @@ public class MainViewController {
@Value("${resource.base-url}")
private String resourceBaseUrl;

@AddLoginStatusAttribute
@AddLoginStatusAttributeToView
@GetMapping("/")
public String showMainPage(Model model) {
List<String> categories = questionSelectorService.getCategories();
@@ -37,7 +37,7 @@ public String showMainPage(Model model) {
return "index";
}

@AddLoginStatusAttribute
@AddLoginStatusAttributeToView
@GetMapping("/license")
public String showLicensePage(Model model) {
List<ResponseLicensesDto> licenseCategories = questionSelectorService.getLicenseCategories()
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@
import com.comssa.api.question.license.service.AdminLicenseQuestionChoiceUpdateService;
import com.comssa.api.question.license.service.AdminLicenseQuestionMakeService;
import com.comssa.persistence.question.common.dto.request.RequestChangeContentDto;
import com.comssa.persistence.question.common.dto.response.ResponseMultipleChoiceQuestionDto;
import com.comssa.persistence.question.common.dto.response.ResponseQuestionChoiceDto;
import com.comssa.persistence.question.common.dto.response.ResponseQuestionDto;
import com.comssa.persistence.question.license.dto.request.RequestMakeLicenseMultipleChoiceQuestionDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -34,7 +34,7 @@ public class AdminLicenseQuestionController {

@ApiOperation("단답형 문제 세션으로 생성")
@PostMapping
public ResponseEntity<List<ResponseQuestionDto>> makeLicenseQuestionOfSession(
public ResponseEntity<List<ResponseMultipleChoiceQuestionDto>> makeLicenseQuestionOfSession(
@RequestBody RequestMakeLicenseMultipleChoiceQuestionDto requestMakeLicenseMultipleChoiceQuestionDto) {
return ResponseEntity.ok(
adminLicenseQuestionMakeService.makeLicenseNormalQuestion(requestMakeLicenseMultipleChoiceQuestionDto));
@@ -51,28 +51,28 @@ public ResponseEntity<String> updateLicenseQuestionWithImage(

@ApiOperation("단답형 문제 상태 업데이트 - 문제 지문 업데이트")
@PatchMapping(value = "/{id}/content")
public ResponseEntity<ResponseQuestionDto> changeContent(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> changeContent(
@PathVariable("id") Long questionId,
@RequestBody RequestChangeContentDto requestChangeContentDto) {
return ResponseEntity.ok(ResponseQuestionDto.forAdmin(
return ResponseEntity.ok(ResponseMultipleChoiceQuestionDto.forLicense(
adminLicenseMuiltipleChoiceQuestionUpdateService.changeContent(questionId, requestChangeContentDto)));
}

@ApiOperation("단답형 문제 상태 업데이트 - 문제 해설 업데이트")
@PatchMapping(value = "/{id}/description")
public ResponseEntity<ResponseQuestionDto> changeDescription(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> changeDescription(
@PathVariable("id") Long questionId,
@RequestBody RequestChangeContentDto requestChangeContentDto) {
return ResponseEntity.ok(ResponseQuestionDto.forAdmin(
return ResponseEntity.ok(ResponseMultipleChoiceQuestionDto.forLicense(
adminLicenseMuiltipleChoiceQuestionUpdateService.changeDescription(questionId, requestChangeContentDto)));
}

@ApiOperation("단답형 문제 상태 업데이트 - 문제 해설 업데이트")
@PatchMapping(value = "/{id}/toggle-approve")
public ResponseEntity<ResponseQuestionDto> toggleIsApprove(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> toggleIsApprove(
@PathVariable("id") Long questionId
) {
return ResponseEntity.ok(ResponseQuestionDto.forAdmin(
return ResponseEntity.ok(ResponseMultipleChoiceQuestionDto.forLicense(
adminLicenseMuiltipleChoiceQuestionUpdateService.toggleApprove(questionId)));
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.comssa.api.question.license.controller;

import com.comssa.api.login.aspect.AddLoginStatusAttribute;
import com.comssa.api.login.aspect.AddLoginStatusAttributeToView;
import com.comssa.api.question.license.service.AdminLicenseQuestionGetService;
import com.comssa.api.question.license.service.LicenseSessionService;
import com.comssa.api.question.license.service.UserLicenseQuestionGetService;
@@ -30,7 +30,7 @@ public class LicenseQuestionGetViewController {
@Value("${resource.base-url}")
private String resourceBaseUrl;

@AddLoginStatusAttribute
@AddLoginStatusAttributeToView
@GetMapping("/question/license/{sessionId}")
public String getLicenseQuestionsBySession(
@PathVariable Long sessionId,
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@

import com.comssa.api.question.common.service.FileUploadService;
import com.comssa.api.question.common.service.implement.QuestionChoiceService;
import com.comssa.persistence.question.common.dto.response.ResponseQuestionDto;
import com.comssa.persistence.question.common.dto.response.ResponseMultipleChoiceQuestionDto;
import com.comssa.persistence.question.license.domain.LicenseCategory;
import com.comssa.persistence.question.license.domain.LicenseMultipleChoiceQuestion;
import com.comssa.persistence.question.license.domain.LicenseSession;
@@ -28,7 +28,7 @@ public class AdminLicenseQuestionMakeService {
private final QuestionChoiceService questionChoiceService;
private final FileUploadService fileUploadService;

public List<ResponseQuestionDto> makeLicenseNormalQuestion(
public List<ResponseMultipleChoiceQuestionDto> makeLicenseNormalQuestion(
RequestMakeLicenseMultipleChoiceQuestionDto requestMakeLicenseMultipleChoiceQuestionDto) {
LicenseSession licenseSession = licenseSessionService.getLicenseSession(
requestMakeLicenseMultipleChoiceQuestionDto.getLicenseSession(),
@@ -42,14 +42,14 @@ public List<ResponseQuestionDto> makeLicenseNormalQuestion(
.collect(Collectors.toList());
}

private ResponseQuestionDto saveNormalLicenseQuestion(
private ResponseMultipleChoiceQuestionDto saveNormalLicenseQuestion(
RequestMakeMultipleChoiceQuestionDto requestMakeMultipleChoiceQuestionDto, LicenseSession licenseSession,
LicenseCategory licenseCategory) {
LicenseMultipleChoiceQuestion licenseMultipleChoiceQuestion = LicenseMultipleChoiceQuestion.makeWithDto(
requestMakeMultipleChoiceQuestionDto, licenseSession, licenseCategory);
licenseMultipleChoiceQuestionRepository.save(licenseMultipleChoiceQuestion);
questionChoiceService.saveWith(requestMakeMultipleChoiceQuestionDto, licenseMultipleChoiceQuestion);
return ResponseQuestionDto.forAdmin(licenseMultipleChoiceQuestion);
return ResponseMultipleChoiceQuestionDto.forLicense(licenseMultipleChoiceQuestion);
}

public String updateLicenseQuestionWithImage(Long licenseQuestionId, MultipartFile file) {
Original file line number Diff line number Diff line change
@@ -6,9 +6,9 @@
import com.comssa.api.question.major.common.exception.DuplicateQuestionException;
import com.comssa.persistence.question.common.dto.request.RequestChangeContentDto;
import com.comssa.persistence.question.common.dto.response.ResponseClassifiedMultipleQuestionDto;
import com.comssa.persistence.question.common.dto.response.ResponseQuestionDto;
import com.comssa.persistence.question.common.dto.response.ResponseMultipleChoiceQuestionDto;
import com.comssa.persistence.question.major.admin.dto.RequestMakeMultipleChoiceQuestionDto;
import com.comssa.persistence.question.major.admin.dto.ResponseMajorQuestionForAdminDto;
import com.comssa.persistence.question.major.admin.dto.ResponseMajorMultipleChoiceQuestionForAdminDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
@@ -47,55 +47,57 @@ public List<ResponseClassifiedMultipleQuestionDto> getAllMajorQuestionForAdmin()

@ApiOperation("단답형 문제 리스트로 생성")
@PostMapping(value = "/question/major-multi")
public ResponseEntity<List<ResponseQuestionDto>> makeMultiMajorQuestion(
public ResponseEntity<List<ResponseMultipleChoiceQuestionDto>> makeMultiMajorQuestion(
@RequestBody List<RequestMakeMultipleChoiceQuestionDto> requestMakeMultipleChoiceQuestionDtos) {
return ResponseEntity.ok(
adminMajorQuestionMakeService.makeMultipleChoiceQuestions(requestMakeMultipleChoiceQuestionDtos)
.stream()
.map(ResponseQuestionDto::forAdmin)
.map(ResponseMultipleChoiceQuestionDto::forMajor)
.collect(Collectors.toList()));
}

@ApiOperation("단답형 문제 단일로 생성")
@PostMapping(value = "/question/major-single")
public ResponseEntity<ResponseQuestionDto> makeSingleNormalQuestion(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> makeSingleNormalQuestion(
@RequestBody RequestMakeMultipleChoiceQuestionDto requestMakeMultipleChoiceQuestionDto) throws
DuplicateQuestionException {
return ResponseEntity.ok(
ResponseQuestionDto.forAdmin(
ResponseMultipleChoiceQuestionDto.forMajor(
adminMajorQuestionMakeService.makeMultipleChoiceQuestion(requestMakeMultipleChoiceQuestionDto)));
}

@ApiOperation("단답형 문제 상태 업데이트 - Approve 토글")
@PatchMapping(value = "/question/major/{id}/toggle-approve")
public ResponseEntity<ResponseQuestionDto> toggleApproveNormalQuestion(@PathVariable("id") Long questionId) {
return ResponseEntity.ok(ResponseMajorQuestionForAdminDto.forAdmin(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> toggleApproveNormalQuestion(
@PathVariable("id") Long questionId) {
return ResponseEntity.ok(ResponseMajorMultipleChoiceQuestionForAdminDto.forMajor(
adminMajorMultipleChoiceQuestionUpdateService.toggleApprove(questionId)));
}

@ApiOperation("단답형 문제 상태 업데이트 - 단답형-주관식 토글")
@PatchMapping(value = "/question/major/{id}/toggle-multiple")
public ResponseEntity<ResponseQuestionDto> toggleCanBeShortAnswered(@PathVariable("id") Long questionId) {
return ResponseEntity.ok(ResponseMajorQuestionForAdminDto.forAdmin(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> toggleCanBeShortAnswered(
@PathVariable("id") Long questionId) {
return ResponseEntity.ok(ResponseMajorMultipleChoiceQuestionForAdminDto.forMajor(
adminMajorMultipleChoiceQuestionUpdateService.toggleCanBeShortAnswered(questionId)));
}

@ApiOperation("단답형 문제 상태 업데이트 - 문제 업데이트")
@PatchMapping(value = "/question/major/{id}/content")
public ResponseEntity<ResponseQuestionDto> changeQuestion(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> changeQuestion(
@PathVariable("id") Long questionId,
@RequestBody RequestChangeContentDto requestChangeContentDto) {
return ResponseEntity.ok(ResponseMajorQuestionForAdminDto.forAdmin(
return ResponseEntity.ok(ResponseMajorMultipleChoiceQuestionForAdminDto.forMajor(
adminMajorMultipleChoiceQuestionUpdateService.changeContent(questionId, requestChangeContentDto)));
}

@ApiOperation("단답형 문제 상태 업데이트 - 해설 업데이트")
@PatchMapping(value = "/question/major/{id}/description")
public ResponseEntity<ResponseQuestionDto> changeDescription(
public ResponseEntity<ResponseMultipleChoiceQuestionDto> changeDescription(
@PathVariable("id") Long questionId,
@RequestBody RequestChangeContentDto requestChangeContentDto
) {
return ResponseEntity.ok(ResponseMajorQuestionForAdminDto.forAdmin(
return ResponseEntity.ok(ResponseMajorMultipleChoiceQuestionForAdminDto.forMajor(
adminMajorMultipleChoiceQuestionUpdateService.changeDescription(questionId, requestChangeContentDto)));
}

Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ public class AdminMajorQuestionViewController {
@Value("${resource.base-url}")
private String resourceBaseUrl;

@GetMapping("/question/update")
@GetMapping("/question/major/update")
public String updateQuestionPage(Model model) {
model.addAttribute("classifiedQuestions",
adminMajorQuestionClassifiedGetService.getClassifiedAllMajorQuestions()
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.comssa.api.question.major.user.controller;

import com.comssa.api.login.aspect.AddLoginStatusAttribute;
import com.comssa.api.login.aspect.AddLoginStatusAttributeToView;
import com.comssa.api.question.major.user.service.implement.BasicMajorQuestionClassifiedGetService;
import com.comssa.persistence.question.common.domain.QuestionCategory;
import com.comssa.persistence.question.common.dto.response.ResponseClassifiedMultipleQuestionDto;
@@ -26,7 +26,7 @@ public class MajorQuestionGetViewController {
@Value("${resource.base-url}")
private String resourceBaseUrl;

@AddLoginStatusAttribute
@AddLoginStatusAttributeToView
@GetMapping("/question/major")
public String getNormalQuestions(
@RequestParam(required = false) List<String> levels,
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import com.comssa.core.chatbot.dto.response.ChatBotResponseDto
import com.comssa.core.chatbot.dto.response.ChatBotResponseDto.Companion.from
import com.comssa.core.chatbot.service.ChatbotService
import io.swagger.annotations.Api
import lombok.RequiredArgsConstructor
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.user.OAuth2User
@@ -15,7 +14,6 @@ import org.springframework.web.bind.annotation.RestController

@RestController
@Api(tags = ["챗봇"])
@RequiredArgsConstructor
class ChatbotController(
private val chatbotService: ChatbotService,
) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.comssa.api.comment.controller

import com.comssa.api.login.aspect.AddLoginStatusAttributeToView
import com.comssa.core.comment.dto.RequestMakeCommentDto
import com.comssa.core.comment.dto.ResponseCommentDto
import com.comssa.core.comment.service.CommentService
import com.comssa.core.question.common.service.QuestionGetService
import com.comssa.persistence.question.common.dto.response.ResponseQuestionDto
import io.swagger.annotations.Api
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.user.OAuth2User
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody

@Controller
@RequestMapping(("/question"))
@Api(tags = ["댓글"])
class CommentController(
private val commentService: CommentService,
private val questionGetService: QuestionGetService,
) {
@ResponseBody
@PostMapping("/{questionId}/comment")
fun addComment(
@PathVariable("questionId") questionId: Long,
@RequestBody requestMakeCommentDto: RequestMakeCommentDto,
@AuthenticationPrincipal user: OAuth2User?,
): ResponseEntity<ResponseCommentDto> =
ResponseEntity.ok(commentService.makeComment(requestMakeCommentDto, questionId, user))

@DeleteMapping("/comment/{commentId}")
fun deleteComment(
@PathVariable("commentId") commentId: Long,
@AuthenticationPrincipal user: OAuth2User?,
): ResponseEntity<Void> {
commentService.deleteComment(commentId)
return ResponseEntity.noContent().build()
}

@GetMapping("/{questionId}/comment")
@AddLoginStatusAttributeToView
fun getComments(
model: Model,
@PathVariable("questionId") questionId: Long,
@AuthenticationPrincipal user: OAuth2User?,
): String {
val comments: List<ResponseCommentDto> = commentService.getComments(questionId, user)
model.addAttribute("multipleChoice", true)
model.addAttribute("comments", comments)
val question = questionGetService.getQuestionByIdFetchIfShould(questionId)
model.addAttribute(
"question",
ResponseQuestionDto.forUser(question),
)
return "commentModal"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.comssa.api.exceptionHandler

import com.comssa.persistence.exception.NotLoginException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler

@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(NotLoginException::class)
fun handleNotLoginException(e: NotLoginException): ResponseEntity<String> =
ResponseEntity(e.message, HttpStatus.UNAUTHORIZED)

@ExceptionHandler(NoSuchElementException::class)
fun handleNoSuchElementException(e: NoSuchElementException): ResponseEntity<String> =
ResponseEntity(e.message, HttpStatus.NOT_FOUND)
}
10 changes: 10 additions & 0 deletions application/src/main/resources/static/css/global-index/style.css
Original file line number Diff line number Diff line change
@@ -137,4 +137,14 @@
height: auto; /* 부모의 높이가 자동으로 조정되도록 설정 */
}

/**
문제 페이지
*/
.right-box {
width: 10%;
}

.left-sidebar {
width: 80%;
}
}
Loading
Loading