diff --git a/.gitignore b/.gitignore index c489d289..e48b6be6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,4 @@ out/ /.nb-gradle/ ### VS Code ### -.vscode/ - - +.vscode/ \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/introduce/common/BaseResponse.java b/src/main/java/umc/kkijuk/server/introduce/common/BaseResponse.java new file mode 100644 index 00000000..155a4765 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/common/BaseResponse.java @@ -0,0 +1,30 @@ +package umc.kkijuk.server.introduce.common; + +import lombok.Getter; + +@Getter +public class BaseResponse { + + private final int status; + private String message; + private T data; + + // Response constructor that includes status, message, and data + public BaseResponse(int status, String message, T data) { + this.status = status; + this.message = message; + this.data = data; + } + + // Response constructor that includes status and message only + public BaseResponse(int status, String message) { + this.status = status; + this.message = message; + } + + // Response constructor that includes status and data only + public BaseResponse(int status, T data) { + this.status = status; + this.data = data; + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/controller/IntroduceController.java b/src/main/java/umc/kkijuk/server/introduce/controller/IntroduceController.java new file mode 100644 index 00000000..0fe7a02c --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/controller/IntroduceController.java @@ -0,0 +1,4 @@ +package umc.kkijuk.server.introduce.controller; + +public class IntroduceController { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/controller/MasterIntroduceController.java b/src/main/java/umc/kkijuk/server/introduce/controller/MasterIntroduceController.java new file mode 100644 index 00000000..b742a926 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/controller/MasterIntroduceController.java @@ -0,0 +1,79 @@ +package umc.kkijuk.server.introduce.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import umc.kkijuk.server.introduce.common.BaseResponse; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; +import umc.kkijuk.server.introduce.dto.MasterIntroduceReqDto; +import umc.kkijuk.server.introduce.dto.MasterIntroduceResDto; +import umc.kkijuk.server.introduce.error.BaseErrorResponse; +import umc.kkijuk.server.introduce.error.BaseException; +import umc.kkijuk.server.introduce.service.MasterIntroduceService; + +import java.util.List; + +@Tag(name = "master", description = "마스터 자기소개서 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/history/intro/master") +public class MasterIntroduceController { + private final MasterIntroduceService masterIntroduceService; + + @PostMapping + public ResponseEntity save(@RequestBody MasterIntroduceReqDto masterIntroduceReqDto){ + try { + MasterIntroduceResDto masterIntroduceResDto = masterIntroduceService.saveMasterIntro(masterIntroduceReqDto); + return ResponseEntity + .status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), "마스터 자기소개서 생성 완료", masterIntroduceResDto)); + } catch (BaseException e) { + return ResponseEntity + .status(e.getCode()) + .body(new BaseErrorResponse(e.getCode(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new BaseErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server error")); + } + } + + @GetMapping + public ResponseEntity get(){ + try { + List masterIntroduce = masterIntroduceService.getMasterIntro(); + return ResponseEntity + .status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), "마스터 자기소개서 조회 완료", masterIntroduce)); + } catch (BaseException e) { + return ResponseEntity + .status(e.getCode()) + .body(new BaseErrorResponse(e.getCode(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new BaseErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server error")); + } + } + + @PatchMapping + public ResponseEntity update(Long id, @RequestBody MasterIntroduceReqDto masterIntroduceReqDto){ + try { + MasterIntroduceResDto masterIntroduceResDto = masterIntroduceService.updateMasterIntro(id, masterIntroduceReqDto); + return ResponseEntity + .status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), "마스터 자기소개서 수정 완료", masterIntroduceResDto)); + } catch (BaseException e) { + return ResponseEntity + .status(e.getCode()) + .body(new BaseErrorResponse(e.getCode(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new BaseErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server error")); + } + } + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/Introduce.java b/src/main/java/umc/kkijuk/server/introduce/domain/Introduce.java new file mode 100644 index 00000000..38dde476 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/Introduce.java @@ -0,0 +1,15 @@ +package umc.kkijuk.server.introduce.domain; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Entity +@Table(name="introduce") +@Getter +public class Introduce { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/IntroduceRepository.java b/src/main/java/umc/kkijuk/server/introduce/domain/IntroduceRepository.java new file mode 100644 index 00000000..d5b690b8 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/IntroduceRepository.java @@ -0,0 +1,6 @@ +package umc.kkijuk.server.introduce.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IntroduceRepository extends JpaRepository { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduce.java b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduce.java new file mode 100644 index 00000000..0ddb65e5 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduce.java @@ -0,0 +1,57 @@ +package umc.kkijuk.server.introduce.domain; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +@Entity +@Table(name = "master_introduce") +@Getter +@NoArgsConstructor +public class MasterIntroduce { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Size(max = 24) + private String oneLiner; + + private String subTitle; + private String content; + + @CreationTimestamp + private LocalDateTime created_at; + + @UpdateTimestamp + private LocalDateTime updated_at; + + @Builder + public MasterIntroduce(String oneLiner, String subTitle, String content) { + this.oneLiner = oneLiner; + this.subTitle = subTitle; + this.content = content; + } + + public String getUpdated_at() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return updated_at != null ? updated_at.format(formatter) : null; + } + + public void update(String oneLiner, String subTitle, String content) { + this.oneLiner = oneLiner; + this.subTitle = subTitle; + this.content = content; + } + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduceRepository.java b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduceRepository.java new file mode 100644 index 00000000..c7001108 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduceRepository.java @@ -0,0 +1,6 @@ +package umc.kkijuk.server.introduce.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MasterIntroduceRepository extends JpaRepository { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceReqDto.java b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceReqDto.java new file mode 100644 index 00000000..14433137 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceReqDto.java @@ -0,0 +1,14 @@ +package umc.kkijuk.server.introduce.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@AllArgsConstructor +@Getter +public class MasterIntroduceReqDto { + private String oneLiner; + private String subTitle; + private String content; +} diff --git a/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceResDto.java b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceResDto.java new file mode 100644 index 00000000..3ef6139a --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceResDto.java @@ -0,0 +1,24 @@ +package umc.kkijuk.server.introduce.dto; + +import lombok.*; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class MasterIntroduceResDto { + private String oneLiner; + private String subTitle; + private String content; + private String updatedAt; + + @Builder + public MasterIntroduceResDto(MasterIntroduce masterIntroduce) { + this.oneLiner = masterIntroduce.getOneLiner(); + this.subTitle = masterIntroduce.getSubTitle(); + this.content = masterIntroduce.getContent(); + this.updatedAt = masterIntroduce.getUpdated_at(); + } + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/error/BaseErrorResponse.java b/src/main/java/umc/kkijuk/server/introduce/error/BaseErrorResponse.java new file mode 100644 index 00000000..a2e015c4 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/error/BaseErrorResponse.java @@ -0,0 +1,18 @@ +package umc.kkijuk.server.introduce.error; +import lombok.Getter; + +@Getter +public class BaseErrorResponse { + private final int status; + private final String message; + + public BaseErrorResponse(int status, String message) { + this.status = status; + this.message = message; + } + + public BaseErrorResponse(BaseException baseException) { + this.status = baseException.getCode(); + this.message = baseException.getMessage(); + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/error/BaseException.java b/src/main/java/umc/kkijuk/server/introduce/error/BaseException.java new file mode 100644 index 00000000..a39c5c37 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/error/BaseException.java @@ -0,0 +1,16 @@ +package umc.kkijuk.server.introduce.error; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BaseException extends RuntimeException{ + private final int code; + private final String message; + + public BaseException(int code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/error/GlobalExceptionHandler.java b/src/main/java/umc/kkijuk/server/introduce/error/GlobalExceptionHandler.java new file mode 100644 index 00000000..275b0914 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/error/GlobalExceptionHandler.java @@ -0,0 +1,21 @@ +package umc.kkijuk.server.introduce.error; + +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 +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + BaseException exception = new BaseException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); + return ResponseEntity.status(exception.getCode()).body(new BaseErrorResponse(exception)); + } + + @ExceptionHandler(BaseException.class) + public ResponseEntity handleBaseException(BaseException e) { + return ResponseEntity.status(e.getCode()).body(new BaseErrorResponse(e)); + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/service/IntroduceService.java b/src/main/java/umc/kkijuk/server/introduce/service/IntroduceService.java new file mode 100644 index 00000000..bef31602 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/service/IntroduceService.java @@ -0,0 +1,4 @@ +package umc.kkijuk.server.introduce.service; + +public class IntroduceService { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/service/MasterIntroduceService.java b/src/main/java/umc/kkijuk/server/introduce/service/MasterIntroduceService.java new file mode 100644 index 00000000..974b8987 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/service/MasterIntroduceService.java @@ -0,0 +1,54 @@ +package umc.kkijuk.server.introduce.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; +import umc.kkijuk.server.introduce.domain.MasterIntroduceRepository; +import umc.kkijuk.server.introduce.dto.MasterIntroduceReqDto; +import umc.kkijuk.server.introduce.dto.MasterIntroduceResDto; +import umc.kkijuk.server.introduce.error.BaseException; + +import java.util.List; + +@RequiredArgsConstructor +@Service +public class MasterIntroduceService{ + private final MasterIntroduceRepository MasterIntroduceRepository; + private final MasterIntroduceRepository masterIntroduceRepository; + + @Transactional + public MasterIntroduceResDto saveMasterIntro(MasterIntroduceReqDto masterIntroduceReqDto) throws Exception{ + if( MasterIntroduceRepository.findAll().stream().count()>0 ){ + throw new BaseException(HttpStatus.CONFLICT.value(), "이미 마스터 자기소개가 존재합니다"); + } + + MasterIntroduce masterIntroduce=MasterIntroduce.builder() + .oneLiner(masterIntroduceReqDto.getOneLiner()) + .content(masterIntroduceReqDto.getContent()) + .subTitle(masterIntroduceReqDto.getSubTitle()) + .build(); + + MasterIntroduceRepository.save(masterIntroduce); + + return new MasterIntroduceResDto(masterIntroduce); + } + + @Transactional + public List getMasterIntro(){ + return MasterIntroduceRepository.findAll(); + } + + @Transactional + public MasterIntroduceResDto updateMasterIntro(Long id, MasterIntroduceReqDto masterIntroduceReqDto) throws Exception{ + MasterIntroduce masterIntroduce=masterIntroduceRepository.findById(id) + .orElseThrow(() -> new BaseException(HttpStatus.NOT_FOUND.value(), "아이디를 다시 확인해주세요")); + + masterIntroduce.update(masterIntroduceReqDto.getOneLiner(), + masterIntroduceReqDto.getSubTitle(), + masterIntroduceReqDto.getContent()); + + return new MasterIntroduceResDto(masterIntroduce); + } +} diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 57a5c698..bfdf7cf7 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -7,5 +7,5 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # spring jpa spring.jpa.database=mysql spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create -spring.jpa.properties.hibernate.format_sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index acf6314b..07a78ad5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,7 +10,7 @@ spring.datasource.password=testPW # spring jpa spring.jpa.database=mysql spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true diff --git a/src/test/java/umc/kkijuk/server/introduce/controller/MasterIntroduceControllerTest.java b/src/test/java/umc/kkijuk/server/introduce/controller/MasterIntroduceControllerTest.java new file mode 100644 index 00000000..b8059ca0 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/introduce/controller/MasterIntroduceControllerTest.java @@ -0,0 +1,98 @@ +package umc.kkijuk.server.introduce.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; +import umc.kkijuk.server.introduce.domain.MasterIntroduceRepository; +import umc.kkijuk.server.introduce.dto.MasterIntroduceReqDto; +import umc.kkijuk.server.introduce.service.MasterIntroduceService; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +@Transactional +class MasterIntroduceControllerTest { + @LocalServerPort + private int port; + @Autowired + private MasterIntroduceRepository masterIntroduceRepository; + @Autowired + private MockMvc mockMvc; + @Autowired + private MasterIntroduceService masterIntroduceService; + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("마스터 자기소개서 생성 테스트") + public void postMaster() throws Exception{ + final String oneLiner= "one-liner-test"; + final String subTitle= "sub-title-test"; + final String content= "content-test"; + + MasterIntroduceReqDto masterIntroduceReqDto= MasterIntroduceReqDto.builder() + .oneLiner(oneLiner) + .subTitle(subTitle) + .content(content) + .build(); + + + mockMvc.perform(post("/history/intro/master") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(masterIntroduceReqDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.oneLiner").value("one-liner-test")) + .andExpect(jsonPath("$.data.subTitle").value("sub-title-test")) + .andExpect(jsonPath("$.data.content").value("content-test")); + } + + @Test + @DisplayName("마스터 자기소개서 수정 테스트") + public void updateMaster() throws Exception { + final String oneLiner = "one-liner-test"; + final String subTitle = "sub-title-test"; + final String content = "content-test"; + + MasterIntroduce masterIntroduce = masterIntroduceRepository.save(MasterIntroduce.builder() + .oneLiner(oneLiner) + .subTitle(subTitle) + .content(content) + .build()); + + Long id = masterIntroduce.getId(); + + String expectedOneLiner = "one-liner2"; + String expectedSubTitle = "sub-title2"; + String expectedContent = "content2"; + + MasterIntroduceReqDto masterIntroduceReqDto = MasterIntroduceReqDto.builder() + .oneLiner(expectedOneLiner) + .subTitle(expectedSubTitle) + .content(expectedContent) + .build(); + + mockMvc.perform(patch("/history/intro/master") + .param("id", id.toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(masterIntroduceReqDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.oneLiner").value(expectedOneLiner)) + .andExpect(jsonPath("$.data.subTitle").value(expectedSubTitle)) + .andExpect(jsonPath("$.data.content").value(expectedContent)); + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index acf6314b..07a78ad5 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -10,7 +10,7 @@ spring.datasource.password=testPW # spring jpa spring.jpa.database=mysql spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true