Skip to content
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
@@ -0,0 +1,89 @@
package com.mtvs.devlinkbackend.question.controller;

import com.mtvs.devlinkbackend.config.JwtUtil;
import com.mtvs.devlinkbackend.oauth2.service.EpicGamesTokenService;
import com.mtvs.devlinkbackend.question.dto.QuestionRegistRequestDTO;
import com.mtvs.devlinkbackend.question.dto.QuestionUpdateRequestDTO;
import com.mtvs.devlinkbackend.question.entity.Question;
import com.mtvs.devlinkbackend.question.service.QuestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/question")
public class QuestionController {

private final QuestionService questionService;
private final JwtUtil jwtUtil;

@Autowired
public QuestionController(QuestionService questionService, JwtUtil jwtUtil) {
this.questionService = questionService;
this.jwtUtil = jwtUtil;
}

// Create a new question
@PostMapping
public ResponseEntity<Question> createQuestion(
@RequestBody QuestionRegistRequestDTO questionRegistRequestDTO,
@RequestHeader(name = "Authorization") String accessToken) throws Exception {

String accountId = jwtUtil.getSubjectFromToken(accessToken);
Question createdQuestion = questionService.registQuestion(questionRegistRequestDTO, accountId);
return ResponseEntity.ok(createdQuestion);
}

// Retrieve a question by ID
@GetMapping("/{questionId}")
public ResponseEntity<Question> getQuestionById(@PathVariable long questionId) {
Question question = questionService.findQuestionByQuestionId(questionId);
if (question != null) {
return ResponseEntity.ok(question);
} else {
return ResponseEntity.notFound().build();
}
}

// Retrieve all questions with pagination
@GetMapping("/all")
public ResponseEntity<List<Question>> getAllQuestionsWithPaging(@RequestParam int page) {
List<Question> questions = questionService.findAllQuestionsWithPaging(page);
return ResponseEntity.ok(questions);
}

// Retrieve questions by account ID with pagination
@GetMapping
public ResponseEntity<List<Question>> getQuestionsByAccountIdWithPaging(
@RequestParam int page,
@RequestHeader(name = "Authorization") String accessToken) throws Exception {

String accountId = jwtUtil.getSubjectFromToken(accessToken);
List<Question> questions = questionService.findQuestionsByAccountIdWithPaging(page, accountId);
return ResponseEntity.ok(questions);
}

// Update a question by ID
@PutMapping("/{id}")
public ResponseEntity<Question> updateQuestion(
@RequestBody QuestionUpdateRequestDTO questionUpdateRequestDTO,
@RequestHeader(name = "Authorization") String accessToken) throws Exception {

String accountId = jwtUtil.getSubjectFromToken(accessToken);
try {
Question updatedQuestion = questionService.updateQuestion(questionUpdateRequestDTO, accountId);
return ResponseEntity.ok(updatedQuestion);
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
}
}

// Delete a question by ID
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteQuestion(@PathVariable long id) {
questionService.deleteQuestion(id);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mtvs.devlinkbackend.question.dto;

import lombok.*;

@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QuestionRegistRequestDTO {
private String title;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mtvs.devlinkbackend.question.dto;

import lombok.*;

@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QuestionUpdateRequestDTO {
private long questionId;
private String title;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.mtvs.devlinkbackend.question.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;

@Table(name = "QUESTION")
@Entity(name = "QUESTION")
@Getter
@NoArgsConstructor
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "QUESTION_ID")
private long questionId;

@Column(name = "TITLE")
private String title;

@Column(name = "CONTENT")
private String content;

@CreationTimestamp
@Column(name = "CREATED_AT", updatable = false)
private LocalDateTime createdAt;

@UpdateTimestamp
@Column(name = "MODIFIED_AT")
private LocalDateTime modifiedAt;

@Column(name = "ACCOUNT_ID") // 사용자 구분
private String accountId;

public Question(String title, String content, String accountId) { // Create용 생성자
this.content = content;
this.accountId = accountId;
}

public void setTitle(String title) {
this.title = title;
}

public void setContent(String content) {
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.mtvs.devlinkbackend.question.repository;

import com.mtvs.devlinkbackend.question.entity.Question;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface QuestionRepository extends JpaRepository<Question, Long> {
Page<Question> findQuestionsByAccountId(String accountId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.mtvs.devlinkbackend.question.service;

import com.mtvs.devlinkbackend.question.dto.QuestionRegistRequestDTO;
import com.mtvs.devlinkbackend.question.dto.QuestionUpdateRequestDTO;
import com.mtvs.devlinkbackend.question.entity.Question;
import com.mtvs.devlinkbackend.question.repository.QuestionRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
public class QuestionService {
private final QuestionRepository questionRepository;

private static int pageSize = 20;

public QuestionService(QuestionRepository questionRepository) {
this.questionRepository = questionRepository;
}

@Transactional
public Question registQuestion(QuestionRegistRequestDTO questionRegistRequestDTO, String accountId) {
return questionRepository.save(
new Question(
questionRegistRequestDTO.getTitle(),
questionRegistRequestDTO.getContent(),
accountId
)
);
}

public Question findQuestionByQuestionId(long questionId) {
return questionRepository.findById(questionId).orElse(null);
}

// findAll with pagination
public List<Question> findAllQuestionsWithPaging(int page) {
Pageable pageable = PageRequest.of(page, pageSize, Sort.by("createdAt").descending());
Page<Question> questionPage = questionRepository.findAll(pageable);
return questionPage.getContent(); // Returns the list of questions
}

// findQuestionsByAccountId with pagination
public List<Question> findQuestionsByAccountIdWithPaging(int page, String accountId) {
Pageable pageable = PageRequest.of(page, pageSize, Sort.by("createdAt").descending());
Page<Question> questionPage = questionRepository.findQuestionsByAccountId(accountId, pageable);
return questionPage.getContent(); // Returns the list of questions for the given accountId
}

public List<Question> findAllQuestions() {
return questionRepository.findAll();
}

@Transactional
public Question updateQuestion(QuestionUpdateRequestDTO questionUpdateRequestDTO, String accountId) {
Optional<Question> foundQuestion =
questionRepository.findById(questionUpdateRequestDTO.getQuestionId());
if (foundQuestion.isPresent()) {
if (foundQuestion.get().getAccountId().equals(accountId)) {
Question question = foundQuestion.get();
question.setTitle(questionUpdateRequestDTO.getTitle());
question.setContent(questionUpdateRequestDTO.getContent());
return question;
} else throw new IllegalArgumentException("Question Update Error : 다른 사용자가 남의 질문 내용 변경 시도");
}
else throw new IllegalArgumentException(
"Question ID : "+ questionUpdateRequestDTO.getQuestionId() +" not found");
}

@Transactional
public void deleteQuestion(Long questionId) {
questionRepository.deleteById(questionId);
System.out.println("질문ID = " + questionId + " ,삭제 시간 : " + LocalDateTime.now());
}
}
21 changes: 20 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,23 @@ spring:
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-name: Epic Games
client-authentication-method: post
client-authentication-method: post
provider:
epicgames:
authorization-uri: https://www.epicgames.com/id/authorize
token-uri: https://api.epicgames.dev/epic/oauth/v2/token
user-info-uri: https://api.epicgames.dev/epic/oauth/v2/userinfo
user-name-attribute: sub
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/devlink
username: root
password: 1234
jpa:
show-sql: true
database: mysql
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.mtvs.devlinkbackend.question;

import com.mtvs.devlinkbackend.question.dto.QuestionRegistRequestDTO;
import com.mtvs.devlinkbackend.question.dto.QuestionUpdateRequestDTO;
import com.mtvs.devlinkbackend.question.service.QuestionService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Stream;

@SpringBootTest
@Transactional
public class QuestionCRUDTest {

@Autowired
private QuestionService questionService;

private static Stream<Arguments> newQuestion() {
return Stream.of(
Arguments.of(new QuestionRegistRequestDTO("질문0", "내용0"), "계정0"),
Arguments.of(new QuestionRegistRequestDTO("질문00", "내용00"), "계정00")
);
}

private static Stream<Arguments> modifiedQuestion() {
return Stream.of(
Arguments.of(new QuestionUpdateRequestDTO(1L,"질문0", "내용0"), "계정1"),
Arguments.of(new QuestionUpdateRequestDTO(2L,"질문00" , "내용00"), "계정1")
);
}

@DisplayName("질문 추가 테스트")
@ParameterizedTest
@MethodSource("newQuestion")
@Order(0)
public void testCreateQuestion(QuestionRegistRequestDTO questionRegistRequestDTO, String accountId) {
Assertions.assertDoesNotThrow(() -> questionService.registQuestion(questionRegistRequestDTO, accountId));
}

@DisplayName("PK로 질문 조회 테스트")
@ValueSource(longs = {1,2})
@ParameterizedTest
@Order(1)
public void testFindQuestionByQuestionId(long questionId) {
Assertions.assertDoesNotThrow(() ->
System.out.println("Question = " + questionService.findQuestionByQuestionId(questionId)));
}

@DisplayName("질문 paging 조회 테스트")
@ValueSource(ints = {0,1})
@ParameterizedTest
@Order(2)
public void testFindQuestionsWithPaging(int page) {
Assertions.assertDoesNotThrow(() ->
System.out.println("Question = " + questionService.findAllQuestionsWithPaging(page)));
}

@DisplayName("계정 ID에 따른 질문 paging 조회 테스트")
@CsvSource({"0,계정1", "0,계정2"})
@ParameterizedTest
@Order(3)
public void testFindQuestionsByAccountIdWithPaging(int page, String accountId) {
Assertions.assertDoesNotThrow(() ->
System.out.println("Question = " + questionService.findQuestionsByAccountIdWithPaging(page, accountId)));
}

@DisplayName("질문 수정 테스트")
@MethodSource("modifiedQuestion")
@ParameterizedTest
@Order(4)
public void testUpdateQuestion(QuestionUpdateRequestDTO questionUpdateRequestDTO, String accountId) {
Assertions.assertDoesNotThrow(() ->
System.out.println(questionService.updateQuestion(questionUpdateRequestDTO, accountId)));
}

@DisplayName("질문 삭제 테스트")
@ValueSource(longs = {0,1})
@ParameterizedTest
@Order(5)
public void testDeleteQuestion(long questionId) {
Assertions.assertDoesNotThrow(() ->
questionService.deleteQuestion(questionId));
}
}