From 06ffe6bd8b562f298055449ddd31efeeafc70f61 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Mon, 30 Sep 2024 17:25:50 +0900 Subject: [PATCH 01/26] =?UTF-8?q?feat:=20JWT=EB=A5=BC=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20Swagger?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/config/SwaggerConfig.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/config/SwaggerConfig.java diff --git a/src/main/java/com/potatocake/everymoment/config/SwaggerConfig.java b/src/main/java/com/potatocake/everymoment/config/SwaggerConfig.java new file mode 100644 index 0000000..bfac4c8 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/config/SwaggerConfig.java @@ -0,0 +1,51 @@ +package com.potatocake.everymoment.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info().title("API").version("1.0")) + .components(new Components() + .addSecuritySchemes("bearerAuth", new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } + + @Bean + public GroupedOpenApi publicApi() { + return GroupedOpenApi.builder() + .group("public-api") + .pathsToMatch("/**") + .addOpenApiCustomizer(addSecurityItemToAllEndpointsExceptLogin()) + .build(); + } + + private OpenApiCustomizer addSecurityItemToAllEndpointsExceptLogin() { + return openApi -> { + SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth"); + openApi.getPaths().forEach((path, item) -> { + if (!"/api/members/login".equals(path)) { + item.readOperations().forEach(operation -> { + operation.addSecurityItem(securityRequirement); + }); + } + }); + }; + } + +} From a74d8cebce3e72b6e7211d932659ba5ae21f8519 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Mon, 30 Sep 2024 17:26:13 +0900 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20MemberController=EC=97=90=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberController.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/com/potatocake/everymoment/controller/MemberController.java b/src/main/java/com/potatocake/everymoment/controller/MemberController.java index 1146a94..3d255dc 100644 --- a/src/main/java/com/potatocake/everymoment/controller/MemberController.java +++ b/src/main/java/com/potatocake/everymoment/controller/MemberController.java @@ -1,6 +1,8 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.request.MemberLoginRequest; +import com.potatocake.everymoment.dto.response.JwtResponse; import com.potatocake.everymoment.dto.response.MemberDetailResponse; import com.potatocake.everymoment.dto.response.MemberResponse; import com.potatocake.everymoment.dto.response.MemberSearchResponse; @@ -8,6 +10,7 @@ import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.MemberService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -16,6 +19,7 @@ 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -28,6 +32,20 @@ public class MemberController { private final MemberService memberService; + @Operation(summary = "로그인", description = "이메일과 닉네임으로 로그인합니다.") +// @ApiResponses(value = { +// @ApiResponse(responseCode = "200", description = "로그인 성공", +// content = @Content(schema = @Schema(implementation = JwtResponse.class))), +// @ApiResponse(responseCode = "401", description = "로그인 실패", +// content = @Content(schema = @Schema(implementation = ErrorResponse.class))) +// }) + @PostMapping("/login") + public ResponseEntity> login(@RequestBody MemberLoginRequest request) { + // 이 메서드는 실제로 호출되지 않습니다. Swagger 문서화를 위해서만 존재합니다. + // 실제 로그인 처리는 LoginFilter에서 이루어집니다. + return ResponseEntity.ok(SuccessResponse.ok(JwtResponse.of("token"))); + } + @GetMapping public ResponseEntity> searchMembers( @RequestParam(required = false) String nickname, From 878450f949443248a3e886841a64a51f47ad0913 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Mon, 30 Sep 2024 17:26:54 +0900 Subject: [PATCH 03/26] =?UTF-8?q?feat:=20POST=EB=A1=9C=EB=A7=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9A=94=EC=B2=AD=EC=9D=B4=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/exception/ErrorCode.java | 4 +++- .../security/filter/LoginFilter.java | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index 3595494..f35cd49 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -45,7 +45,9 @@ public enum ErrorCode { INVALID_FILE_TYPE("이미지 파일 형식만 첨부가 가능합니다. (JPEG, PNG)", UNSUPPORTED_MEDIA_TYPE), FILE_STORE_FAILED("파일 저장에 실패했습니다.", INTERNAL_SERVER_ERROR), - INFO_REQUIRED("정보를 입력해 주세요.", BAD_REQUEST); + INFO_REQUIRED("정보를 입력해 주세요.", BAD_REQUEST), + + METHOD_NOT_ALLOWED("지원하지 않는 HTTP 메소드입니다.", HttpStatus.METHOD_NOT_ALLOWED); private final String message; private final HttpStatus status; diff --git a/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java b/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java index 6077c30..18760f8 100644 --- a/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java +++ b/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java @@ -20,6 +20,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -32,6 +33,8 @@ public class LoginFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final MemberRepository memberRepository; + private boolean postOnly = true; + public LoginFilter(String filterProcessesUrl, ObjectMapper objectMapper, JwtUtil jwtUtil, MemberRepository memberRepository, AuthenticationManager authenticationManager) { setFilterProcessesUrl(filterProcessesUrl); @@ -44,6 +47,10 @@ public LoginFilter(String filterProcessesUrl, ObjectMapper objectMapper, JwtUtil @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + if (this.postOnly && !request.getMethod().equals("POST")) { + throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); + } + MemberLoginRequest loginRequest = getLoginRequest(request); if (loginRequest.getEmail() != null && loginRequest.getNickname() != null) { @@ -76,13 +83,20 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { - ErrorCode loginFailed = ErrorCode.LOGIN_FAILED; + ErrorCode errorCode; + + if (failed instanceof AuthenticationServiceException && + failed.getMessage().startsWith("Authentication method not supported")) { + errorCode = ErrorCode.METHOD_NOT_ALLOWED; + } else { + errorCode = ErrorCode.LOGIN_FAILED; + } response.setContentType(APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); - response.setStatus(loginFailed.getStatus().value()); + response.setStatus(errorCode.getStatus().value()); - objectMapper.writeValue(response.getWriter(), getErrorResponse(loginFailed)); + objectMapper.writeValue(response.getWriter(), getErrorResponse(errorCode)); } private MemberLoginRequest getLoginRequest(HttpServletRequest request) { From 2a546ddf2bfaad9e691c3e06186c4ca64811a6de Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Mon, 30 Sep 2024 17:29:25 +0900 Subject: [PATCH 04/26] =?UTF-8?q?refactor:=20cicd=20name=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/master_weekly_cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/master_weekly_cicd.yml b/.github/workflows/master_weekly_cicd.yml index bca581a..1c4ad9e 100644 --- a/.github/workflows/master_weekly_cicd.yml +++ b/.github/workflows/master_weekly_cicd.yml @@ -1,4 +1,4 @@ -name: master 브랜치 merge 시 CI/CD 파이프라인 +name: master 및 weekly 브랜치 merge 시 CI/CD 파이프라인 on: push: From 1b49e90bc59222982c37ca1926347c39c93adfa9 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 00:18:09 +0900 Subject: [PATCH 05/26] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FileController.java | 32 +++++++++++++++++ .../dto/response/FileResponse.java | 11 ++++++ .../potatocake/everymoment/entity/Diary.java | 6 +++- .../potatocake/everymoment/entity/File.java | 30 ++++++++++++++++ .../repository/FileRepository.java | 11 ++++++ .../everymoment/service/DiaryService.java | 11 ++---- .../everymoment/service/FileService.java | 34 +++++++++++++++++++ 7 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/potatocake/everymoment/controller/FileController.java create mode 100644 src/main/java/com/potatocake/everymoment/entity/File.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/FileRepository.java create mode 100644 src/main/java/com/potatocake/everymoment/service/FileService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/FileController.java b/src/main/java/com/potatocake/everymoment/controller/FileController.java new file mode 100644 index 0000000..df5bdf0 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/FileController.java @@ -0,0 +1,32 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.response.FileResponse; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FileService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/diaries/{diaryId}/files") +@RestController +public class FileController { + + private final FileService fileService; + + @GetMapping + public ResponseEntity> getFiles(@PathVariable Long diaryId, + @AuthenticationPrincipal MemberDetails memberDetails) { + List files = fileService.getFiles(diaryId, memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(files)); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/FileResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/FileResponse.java index 4b71b31..bbea9ab 100644 --- a/src/main/java/com/potatocake/everymoment/dto/response/FileResponse.java +++ b/src/main/java/com/potatocake/everymoment/dto/response/FileResponse.java @@ -1,12 +1,23 @@ package com.potatocake.everymoment.dto.response; +import com.potatocake.everymoment.entity.File; import lombok.Builder; import lombok.Getter; @Getter @Builder public class FileResponse { + private Long id; private String imageUrl; private int order; + + public static FileResponse toResponseDto(File file) { + return FileResponse.builder() + .id(file.getId()) + .imageUrl(file.getImageUrl()) + .order(file.getOrder()) + .build(); + } + } diff --git a/src/main/java/com/potatocake/everymoment/entity/Diary.java b/src/main/java/com/potatocake/everymoment/entity/Diary.java index b0b85ee..a0ae427 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Diary.java +++ b/src/main/java/com/potatocake/everymoment/entity/Diary.java @@ -3,7 +3,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.ForeignKey; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -99,4 +98,9 @@ public void toggleBookmark() { public void togglePublic() { this.isPublic = !this.isPublic; } + + public boolean checkOwner(Long memberId) { + return this.memberId.getId().equals(memberId); + } + } diff --git a/src/main/java/com/potatocake/everymoment/entity/File.java b/src/main/java/com/potatocake/everymoment/entity/File.java new file mode 100644 index 0000000..b298360 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/entity/File.java @@ -0,0 +1,30 @@ +package com.potatocake.everymoment.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import lombok.Getter; + +@Getter +@Entity +public class File extends BaseTimeEntity { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Diary diary; + + @Lob + private String imageUrl; + + @Column(name = "\"order\"") + private Integer order; + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/FileRepository.java b/src/main/java/com/potatocake/everymoment/repository/FileRepository.java new file mode 100644 index 0000000..49a4df7 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/FileRepository.java @@ -0,0 +1,11 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.File; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FileRepository extends JpaRepository { + + List findByDiaryId(Long diaryId); + +} diff --git a/src/main/java/com/potatocake/everymoment/service/DiaryService.java b/src/main/java/com/potatocake/everymoment/service/DiaryService.java index 2adb77d..e8bf635 100644 --- a/src/main/java/com/potatocake/everymoment/service/DiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/DiaryService.java @@ -5,9 +5,6 @@ import com.potatocake.everymoment.dto.request.DiaryManualCreateRequest; import com.potatocake.everymoment.dto.response.CategoryResponse; import com.potatocake.everymoment.dto.response.FileResponse; -import com.potatocake.everymoment.dto.response.FriendDiariesResponse; -import com.potatocake.everymoment.dto.response.FriendDiarySimpleResponse; -import com.potatocake.everymoment.dto.response.FriendDiaryResponse; import com.potatocake.everymoment.dto.response.MyDiariesResponse; import com.potatocake.everymoment.dto.response.MyDiaryResponse; import com.potatocake.everymoment.dto.response.MyDiarySimpleResponse; @@ -21,15 +18,11 @@ import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.repository.DiaryCategoryRepository; import com.potatocake.everymoment.repository.DiaryRepository; -import com.potatocake.everymoment.repository.MemberRepository; import com.potatocake.everymoment.repository.NotificationRepository; import com.potatocake.everymoment.security.MemberDetails; -import com.potatocake.everymoment.util.JwtUtil; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -207,7 +200,7 @@ public void togglePrivacy(Long id) { } // 로그인한 유저의 일기가 맞는지 확인 후 일기 반환 - private Diary getExistDiary(Long diaryId){ + private Diary getExistDiary(Long diaryId) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); Member currentMember = memberDetails.getMember(); @@ -215,7 +208,7 @@ private Diary getExistDiary(Long diaryId){ Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); - if(!Objects.equals(currentMember.getId(), diary.getMemberId().getId())){ + if (!Objects.equals(currentMember.getId(), diary.getMemberId().getId())) { throw new GlobalException(ErrorCode.DIARY_NOT_FOUND); } diff --git a/src/main/java/com/potatocake/everymoment/service/FileService.java b/src/main/java/com/potatocake/everymoment/service/FileService.java new file mode 100644 index 0000000..d25d00a --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/FileService.java @@ -0,0 +1,34 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.dto.response.FileResponse; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.FileRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class FileService { + + private final FileRepository fileRepository; + private final DiaryRepository diaryRepository; + + @Transactional(readOnly = true) + public List getFiles(Long diaryId, Long memberId) { + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + + diary.checkOwner(memberId); + + return fileRepository.findByDiaryId(diaryId).stream() + .map(FileResponse::toResponseDto) + .toList(); + } + +} From 2c23cbb2243eb5b821fb0a235973680feba2d5e5 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 00:51:51 +0900 Subject: [PATCH 06/26] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FileController.java | 21 ++++++++-- .../everymoment/dto/request/FileRequest.java | 2 +- .../potatocake/everymoment/entity/File.java | 12 ++++++ .../everymoment/service/FileService.java | 42 +++++++++++++++++-- 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/FileController.java b/src/main/java/com/potatocake/everymoment/controller/FileController.java index df5bdf0..d089551 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FileController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FileController.java @@ -1,17 +1,22 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.request.FileRequest; import com.potatocake.everymoment.dto.response.FileResponse; import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.FileService; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; 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.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor @RequestMapping("/api/diaries/{diaryId}/files") @@ -21,12 +26,22 @@ public class FileController { private final FileService fileService; @GetMapping - public ResponseEntity> getFiles(@PathVariable Long diaryId, - @AuthenticationPrincipal MemberDetails memberDetails) { - List files = fileService.getFiles(diaryId, memberDetails.getId()); + public ResponseEntity> getFiles(@PathVariable Long diaryId) { + List files = fileService.getFiles(diaryId); return ResponseEntity.ok() .body(SuccessResponse.ok(files)); } + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity uploadFiles(@PathVariable Long diaryId, + @AuthenticationPrincipal MemberDetails memberDetails, + @RequestPart List files, + @RequestPart List info) { + fileService.uploadFiles(diaryId, memberDetails.getId(), files, info); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/dto/request/FileRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/FileRequest.java index 8c2b494..9d22d27 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/FileRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/FileRequest.java @@ -4,6 +4,6 @@ @Getter public class FileRequest { - private String imageUrl; + private String filename; private int order; } diff --git a/src/main/java/com/potatocake/everymoment/entity/File.java b/src/main/java/com/potatocake/everymoment/entity/File.java index b298360..8bd1ba4 100644 --- a/src/main/java/com/potatocake/everymoment/entity/File.java +++ b/src/main/java/com/potatocake/everymoment/entity/File.java @@ -8,9 +8,13 @@ import jakarta.persistence.Id; import jakarta.persistence.Lob; import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class File extends BaseTimeEntity { @@ -27,4 +31,12 @@ public class File extends BaseTimeEntity { @Column(name = "\"order\"") private Integer order; + @Builder + public File(Long id, Diary diary, String imageUrl, Integer order) { + this.id = id; + this.diary = diary; + this.imageUrl = imageUrl; + this.order = order; + } + } diff --git a/src/main/java/com/potatocake/everymoment/service/FileService.java b/src/main/java/com/potatocake/everymoment/service/FileService.java index d25d00a..961e382 100644 --- a/src/main/java/com/potatocake/everymoment/service/FileService.java +++ b/src/main/java/com/potatocake/everymoment/service/FileService.java @@ -1,15 +1,22 @@ package com.potatocake.everymoment.service; +import com.potatocake.everymoment.dto.request.FileRequest; import com.potatocake.everymoment.dto.response.FileResponse; import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.File; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.repository.DiaryRepository; import com.potatocake.everymoment.repository.FileRepository; +import com.potatocake.everymoment.util.S3FileUploader; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor @Transactional @@ -18,17 +25,44 @@ public class FileService { private final FileRepository fileRepository; private final DiaryRepository diaryRepository; + private final S3FileUploader uploader; @Transactional(readOnly = true) - public List getFiles(Long diaryId, Long memberId) { + public List getFiles(Long diaryId) { + return fileRepository.findByDiaryId(diaryId).stream() + .map(FileResponse::toResponseDto) + .toList(); + } + + public void uploadFiles(Long diaryId, Long memberId, List files, List infos) { Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); diary.checkOwner(memberId); - return fileRepository.findByDiaryId(diaryId).stream() - .map(FileResponse::toResponseDto) - .toList(); + Map fileMap = files.stream() + .collect(Collectors.toMap(MultipartFile::getOriginalFilename, f -> f, (f1, f2) -> f1)); + + List fileEntities = new ArrayList<>(); + + for (FileRequest info : infos) { + MultipartFile file = fileMap.get(info.getFilename()); + if (file == null) { + throw new GlobalException(ErrorCode.FILE_NOT_FOUND); + } + + String url = uploader.uploadFile(file); + + File fileEntity = File.builder() + .diary(diary) + .imageUrl(url) + .order(info.getOrder()) + .build(); + + fileEntities.add(fileEntity); + } + + fileRepository.saveAll(fileEntities); } } From f045230a3e90406dd34a4fc1b7f6610656f119c3 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 01:03:19 +0900 Subject: [PATCH 07/26] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/FileController.java | 12 ++++++++++++ .../everymoment/repository/FileRepository.java | 3 +++ .../potatocake/everymoment/service/FileService.java | 11 +++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/main/java/com/potatocake/everymoment/controller/FileController.java b/src/main/java/com/potatocake/everymoment/controller/FileController.java index d089551..e17bd70 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FileController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FileController.java @@ -13,6 +13,7 @@ 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.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; @@ -44,4 +45,15 @@ public ResponseEntity uploadFiles(@PathVariable Long diaryId, .body(SuccessResponse.ok()); } + @PutMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity updateFiles(@PathVariable Long diaryId, + @AuthenticationPrincipal MemberDetails memberDetails, + @RequestPart List files, + @RequestPart List info) { + fileService.updateFiles(diaryId, memberDetails.getId(), files, info); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/repository/FileRepository.java b/src/main/java/com/potatocake/everymoment/repository/FileRepository.java index 49a4df7..04fac6d 100644 --- a/src/main/java/com/potatocake/everymoment/repository/FileRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/FileRepository.java @@ -1,5 +1,6 @@ package com.potatocake.everymoment.repository; +import com.potatocake.everymoment.entity.Diary; import com.potatocake.everymoment.entity.File; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,4 +9,6 @@ public interface FileRepository extends JpaRepository { List findByDiaryId(Long diaryId); + void deleteByDiary(Diary diary); + } diff --git a/src/main/java/com/potatocake/everymoment/service/FileService.java b/src/main/java/com/potatocake/everymoment/service/FileService.java index 961e382..771129e 100644 --- a/src/main/java/com/potatocake/everymoment/service/FileService.java +++ b/src/main/java/com/potatocake/everymoment/service/FileService.java @@ -65,4 +65,15 @@ public void uploadFiles(Long diaryId, Long memberId, List files, fileRepository.saveAll(fileEntities); } + public void updateFiles(Long diaryId, Long memberId, List files, List infos) { + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + + diary.checkOwner(memberId); + + fileRepository.deleteByDiary(diary); + + uploadFiles(diaryId, memberId, files, infos); + } + } From 892f0b2b1d66f5af043ea9c6b1b0aa6b42e19ce5 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 16:22:49 +0900 Subject: [PATCH 08/26] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 32 +++++++++++++++++++ .../dto/request/CategoryCreateRequest.java | 23 +++++++++++++ .../everymoment/entity/Category.java | 8 +++++ .../everymoment/exception/ErrorResponse.java | 9 +++++- .../repository/CategoryRepository.java | 7 ++++ .../everymoment/service/CategoryService.java | 28 ++++++++++++++++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/potatocake/everymoment/controller/CategoryController.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java create mode 100644 src/main/java/com/potatocake/everymoment/service/CategoryService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java new file mode 100644 index 0000000..bd57bbb --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java @@ -0,0 +1,32 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.request.CategoryCreateRequest; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.CategoryService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/categories") +@RestController +public class CategoryController { + + private final CategoryService categoryService; + + @PostMapping + public ResponseEntity addCategory(@RequestBody @Valid CategoryCreateRequest request, + @AuthenticationPrincipal MemberDetails memberDetails) { + categoryService.addCategory(memberDetails.getId(), request); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java new file mode 100644 index 0000000..e19e82f --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java @@ -0,0 +1,23 @@ +package com.potatocake.everymoment.dto.request; + +import com.potatocake.everymoment.entity.Category; +import com.potatocake.everymoment.entity.Member; +import jakarta.validation.constraints.NotEmpty; +import lombok.Getter; +import org.hibernate.validator.constraints.Length; + +@Getter +public class CategoryCreateRequest { + + @Length(max = 50) + @NotEmpty + private String categoryName; + + public Category toEntity(Member member) { + return Category.builder() + .member(member) + .categoryName(categoryName) + .build(); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/entity/Category.java b/src/main/java/com/potatocake/everymoment/entity/Category.java index 0a45878..7afae12 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Category.java +++ b/src/main/java/com/potatocake/everymoment/entity/Category.java @@ -2,9 +2,13 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -23,4 +27,8 @@ public class Category { @Column(length = 50, nullable = false) private String categoryName; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(foreignKey = @ForeignKey(name = "fk_member_id"), nullable = false) + private Member member; } diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorResponse.java b/src/main/java/com/potatocake/everymoment/exception/ErrorResponse.java index 6d46f8d..54b7db8 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorResponse.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorResponse.java @@ -2,12 +2,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import java.util.HashMap; import java.util.Map; import lombok.Builder; import lombok.Getter; @Getter -@Builder @JsonInclude(Include.NON_NULL) public class ErrorResponse { @@ -15,6 +15,13 @@ public class ErrorResponse { private String message; private Map validation; + @Builder + public ErrorResponse(int code, String message) { + this.code = code; + this.message = message; + this.validation = new HashMap<>(); + } + public void addValidation(String fieldName, String errorMessage) { this.validation.put(fieldName, errorMessage); } diff --git a/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java b/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java new file mode 100644 index 0000000..4f9fec1 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java @@ -0,0 +1,7 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/src/main/java/com/potatocake/everymoment/service/CategoryService.java b/src/main/java/com/potatocake/everymoment/service/CategoryService.java new file mode 100644 index 0000000..95aaf95 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/CategoryService.java @@ -0,0 +1,28 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.dto.request.CategoryCreateRequest; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.CategoryRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class CategoryService { + + private final CategoryRepository categoryRepository; + private final MemberRepository memberRepository; + + public void addCategory(Long memberId, CategoryCreateRequest request) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + categoryRepository.save(request.toEntity(member)); + } + +} From 1052bae01a213ffcea7588e02bb8e73f923e4ec7 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 16:29:04 +0900 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/CategoryController.java | 12 ++++++++++++ .../everymoment/dto/response/CategoryResponse.java | 11 ++++++++++- .../everymoment/repository/CategoryRepository.java | 4 ++++ .../everymoment/service/CategoryService.java | 8 ++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java index bd57bbb..cd5acf2 100644 --- a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java +++ b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java @@ -2,12 +2,15 @@ import com.potatocake.everymoment.dto.SuccessResponse; import com.potatocake.everymoment.dto.request.CategoryCreateRequest; +import com.potatocake.everymoment.dto.response.CategoryResponse; import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.CategoryService; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -20,6 +23,15 @@ public class CategoryController { private final CategoryService categoryService; + @GetMapping + public ResponseEntity> getCategories( + @AuthenticationPrincipal MemberDetails memberDetails) { + List categories = categoryService.getCategories(memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(categories)); + } + @PostMapping public ResponseEntity addCategory(@RequestBody @Valid CategoryCreateRequest request, @AuthenticationPrincipal MemberDetails memberDetails) { diff --git a/src/main/java/com/potatocake/everymoment/dto/response/CategoryResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/CategoryResponse.java index c57cd6a..665e76f 100644 --- a/src/main/java/com/potatocake/everymoment/dto/response/CategoryResponse.java +++ b/src/main/java/com/potatocake/everymoment/dto/response/CategoryResponse.java @@ -1,5 +1,6 @@ package com.potatocake.everymoment.dto.response; +import com.potatocake.everymoment.entity.Category; import lombok.Builder; import lombok.Getter; @@ -8,4 +9,12 @@ public class CategoryResponse { private long id; private String categoryName; -} \ No newline at end of file + + public static CategoryResponse from(Category category) { + return CategoryResponse.builder() + .id(category.getId()) + .categoryName(category.getCategoryName()) + .build(); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java b/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java index 4f9fec1..1b3e4db 100644 --- a/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/CategoryRepository.java @@ -1,7 +1,11 @@ package com.potatocake.everymoment.repository; import com.potatocake.everymoment.entity.Category; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface CategoryRepository extends JpaRepository { + + List findByMemberId(Long memberId); + } diff --git a/src/main/java/com/potatocake/everymoment/service/CategoryService.java b/src/main/java/com/potatocake/everymoment/service/CategoryService.java index 95aaf95..771c051 100644 --- a/src/main/java/com/potatocake/everymoment/service/CategoryService.java +++ b/src/main/java/com/potatocake/everymoment/service/CategoryService.java @@ -1,11 +1,13 @@ package com.potatocake.everymoment.service; import com.potatocake.everymoment.dto.request.CategoryCreateRequest; +import com.potatocake.everymoment.dto.response.CategoryResponse; import com.potatocake.everymoment.entity.Member; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.repository.CategoryRepository; import com.potatocake.everymoment.repository.MemberRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,4 +27,10 @@ public void addCategory(Long memberId, CategoryCreateRequest request) { categoryRepository.save(request.toEntity(member)); } + public List getCategories(Long memberId) { + return categoryRepository.findByMemberId(memberId).stream() + .map(CategoryResponse::from) + .toList(); + } + } From 2975e5fe917e4b66aba8709d536260b5544a6720 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 16:36:17 +0900 Subject: [PATCH 10/26] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/CategoryController.java | 12 ++++++++++++ .../com/potatocake/everymoment/entity/Category.java | 13 +++++++++++++ .../potatocake/everymoment/exception/ErrorCode.java | 5 ++++- .../everymoment/service/CategoryService.java | 10 ++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java index cd5acf2..786ec56 100644 --- a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java +++ b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java @@ -11,6 +11,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +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; @@ -41,4 +43,14 @@ public ResponseEntity addCategory(@RequestBody @Valid CategoryC .body(SuccessResponse.ok()); } + @PatchMapping("/{categoryId}") + public ResponseEntity updateCategory(@PathVariable Long categoryId, + @RequestBody @Valid CategoryCreateRequest request, + @AuthenticationPrincipal MemberDetails memberDetails) { + categoryService.updateCategory(categoryId, memberDetails.getId(), request); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/entity/Category.java b/src/main/java/com/potatocake/everymoment/entity/Category.java index 7afae12..335233d 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Category.java +++ b/src/main/java/com/potatocake/everymoment/entity/Category.java @@ -1,5 +1,7 @@ package com.potatocake.everymoment.entity; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -31,4 +33,15 @@ public class Category { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(foreignKey = @ForeignKey(name = "fk_member_id"), nullable = false) private Member member; + + public void checkOwner(Long memberId) { + if (!member.getId().equals(memberId)) { + throw new GlobalException(ErrorCode.CATEGORY_NOT_OWNER); + } + } + + public void update(String categoryName) { + this.categoryName = categoryName; + } + } diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index f35cd49..ccee02c 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -47,7 +47,10 @@ public enum ErrorCode { INFO_REQUIRED("정보를 입력해 주세요.", BAD_REQUEST), - METHOD_NOT_ALLOWED("지원하지 않는 HTTP 메소드입니다.", HttpStatus.METHOD_NOT_ALLOWED); + METHOD_NOT_ALLOWED("지원하지 않는 HTTP 메소드입니다.", HttpStatus.METHOD_NOT_ALLOWED), + + /* CategoryService */ + CATEGORY_NOT_OWNER("본인의 카테고리만 수정할 수 있습니다.", HttpStatus.FORBIDDEN); private final String message; private final HttpStatus status; diff --git a/src/main/java/com/potatocake/everymoment/service/CategoryService.java b/src/main/java/com/potatocake/everymoment/service/CategoryService.java index 771c051..ea2efdb 100644 --- a/src/main/java/com/potatocake/everymoment/service/CategoryService.java +++ b/src/main/java/com/potatocake/everymoment/service/CategoryService.java @@ -2,6 +2,7 @@ import com.potatocake.everymoment.dto.request.CategoryCreateRequest; import com.potatocake.everymoment.dto.response.CategoryResponse; +import com.potatocake.everymoment.entity.Category; import com.potatocake.everymoment.entity.Member; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; @@ -33,4 +34,13 @@ public List getCategories(Long memberId) { .toList(); } + public void updateCategory(Long categoryId, Long memberId, CategoryCreateRequest request) { + Category category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new GlobalException(ErrorCode.CATEGORY_NOT_FOUND)); + + category.checkOwner(memberId); + + category.update(request.getCategoryName()); + } + } From cfbdfe90b2305f35c7801ed033dee1a104f8447f Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 16:39:01 +0900 Subject: [PATCH 11/26] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=82=AD=EC=A0=9C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/CategoryController.java | 10 ++++++++++ .../everymoment/service/CategoryService.java | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java index 786ec56..36a19fa 100644 --- a/src/main/java/com/potatocake/everymoment/controller/CategoryController.java +++ b/src/main/java/com/potatocake/everymoment/controller/CategoryController.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -53,4 +54,13 @@ public ResponseEntity updateCategory(@PathVariable Long categor .body(SuccessResponse.ok()); } + @DeleteMapping("/{categoryId}") + public ResponseEntity deleteCategory(@PathVariable Long categoryId, + @AuthenticationPrincipal MemberDetails memberDetails) { + categoryService.deleteCategory(categoryId, memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/service/CategoryService.java b/src/main/java/com/potatocake/everymoment/service/CategoryService.java index ea2efdb..69d7224 100644 --- a/src/main/java/com/potatocake/everymoment/service/CategoryService.java +++ b/src/main/java/com/potatocake/everymoment/service/CategoryService.java @@ -43,4 +43,13 @@ public void updateCategory(Long categoryId, Long memberId, CategoryCreateRequest category.update(request.getCategoryName()); } + public void deleteCategory(Long categoryId, Long memberId) { + Category category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new GlobalException(ErrorCode.CATEGORY_NOT_FOUND)); + + category.checkOwner(memberId); + + categoryRepository.delete(category); + } + } From 7ce42e13c7469ff0c36a0a942bf22b4655735889 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 17:01:26 +0900 Subject: [PATCH 12/26] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9A=94=EC=B2=AD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 36 +++++++++++++++++ .../everymoment/entity/FriendRequest.java | 36 +++++++++++++++++ .../everymoment/exception/ErrorCode.java | 8 +++- .../repository/FriendRequestRepository.java | 8 ++++ .../service/FriendRequestService.java | 40 +++++++++++++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java create mode 100644 src/main/java/com/potatocake/everymoment/entity/FriendRequest.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java create mode 100644 src/main/java/com/potatocake/everymoment/service/FriendRequestService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java new file mode 100644 index 0000000..34053c7 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -0,0 +1,36 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FriendRequestService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/members/{memberId}/friend-requests") +@RestController +public class FriendRequestController { + + private final FriendRequestService friendRequestService; + + @PostMapping + public ResponseEntity sendFriendRequest(@PathVariable Long memberId, + @AuthenticationPrincipal MemberDetails memberDetails) { + if (memberDetails.getId().equals(memberId)) { + throw new GlobalException(ErrorCode.SELF_FRIEND_REQUEST); + } + + friendRequestService.sendFriendRequest(memberDetails.getId(), memberId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/entity/FriendRequest.java b/src/main/java/com/potatocake/everymoment/entity/FriendRequest.java new file mode 100644 index 0000000..5ec955d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/entity/FriendRequest.java @@ -0,0 +1,36 @@ +package com.potatocake.everymoment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class FriendRequest { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Member sender; + + @ManyToOne(fetch = FetchType.LAZY) + private Member receiver; + + @Builder + public FriendRequest(Long id, Member sender, Member receiver) { + this.id = id; + this.sender = sender; + this.receiver = receiver; + } + +} diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index ccee02c..0e76866 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -50,7 +50,13 @@ public enum ErrorCode { METHOD_NOT_ALLOWED("지원하지 않는 HTTP 메소드입니다.", HttpStatus.METHOD_NOT_ALLOWED), /* CategoryService */ - CATEGORY_NOT_OWNER("본인의 카테고리만 수정할 수 있습니다.", HttpStatus.FORBIDDEN); + CATEGORY_NOT_OWNER("본인의 카테고리만 수정할 수 있습니다.", HttpStatus.FORBIDDEN), + + /* FriendRequestController */ + SELF_FRIEND_REQUEST("자기 자신에게 친구 요청을 보낼 수 없습니다.", BAD_REQUEST), + + /* FriendRequestService */ + FRIEND_REQUEST_ALREADY_EXISTS("이미 친구 요청을 보냈습니다.", CONFLICT); private final String message; private final HttpStatus status; diff --git a/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java new file mode 100644 index 0000000..0557d7f --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java @@ -0,0 +1,8 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.FriendRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FriendRequestRepository extends JpaRepository { + boolean existsBySenderIdAndReceiverId(Long senderId, Long receiverId); +} diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java new file mode 100644 index 0000000..d6c55f7 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -0,0 +1,40 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.entity.FriendRequest; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.FriendRequestRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class FriendRequestService { + + private final FriendRequestRepository friendRequestRepository; + private final MemberRepository memberRepository; + + public void sendFriendRequest(Long senderId, Long receiverId) { + boolean isAlreadySend = friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId); + + if (isAlreadySend) { + throw new GlobalException(ErrorCode.FRIEND_REQUEST_ALREADY_EXISTS); + } + + Member sender = memberRepository.findById(senderId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Member receiver = memberRepository.findById(receiverId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + friendRequestRepository.save(FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build()); + } + +} From 1b9bd3234bf5320826416e65365013cdff220ba4 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 17:19:08 +0900 Subject: [PATCH 13/26] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=88=98=EB=9D=BD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 13 ++++++-- .../everymoment/exception/ErrorCode.java | 3 +- .../service/FriendRequestService.java | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java index 34053c7..65ce16c 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -10,17 +10,15 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor -@RequestMapping("/api/members/{memberId}/friend-requests") @RestController public class FriendRequestController { private final FriendRequestService friendRequestService; - @PostMapping + @PostMapping("/api/members/{memberId}/friend-requests") public ResponseEntity sendFriendRequest(@PathVariable Long memberId, @AuthenticationPrincipal MemberDetails memberDetails) { if (memberDetails.getId().equals(memberId)) { @@ -33,4 +31,13 @@ public ResponseEntity sendFriendRequest(@PathVariable Long memb .body(SuccessResponse.ok()); } + @PostMapping("/api/friend-requests/{requestId}/accept") + public ResponseEntity acceptFriendRequest(@PathVariable Long requestId, + @AuthenticationPrincipal MemberDetails memberDetails) { + friendRequestService.acceptFriendRequest(requestId, memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index 0e76866..365d29a 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -56,7 +56,8 @@ public enum ErrorCode { SELF_FRIEND_REQUEST("자기 자신에게 친구 요청을 보낼 수 없습니다.", BAD_REQUEST), /* FriendRequestService */ - FRIEND_REQUEST_ALREADY_EXISTS("이미 친구 요청을 보냈습니다.", CONFLICT); + FRIEND_REQUEST_ALREADY_EXISTS("이미 친구 요청을 보냈습니다.", CONFLICT), + FRIEND_REQUEST_NOT_FOUND("존재하지 않는 친구 요청입니다.", NOT_FOUND); private final String message; private final HttpStatus status; diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java index d6c55f7..1a8fa6d 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -1,9 +1,11 @@ package com.potatocake.everymoment.service; +import com.potatocake.everymoment.entity.Friend; import com.potatocake.everymoment.entity.FriendRequest; import com.potatocake.everymoment.entity.Member; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.FriendRepository; import com.potatocake.everymoment.repository.FriendRequestRepository; import com.potatocake.everymoment.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -17,6 +19,7 @@ public class FriendRequestService { private final FriendRequestRepository friendRequestRepository; private final MemberRepository memberRepository; + private final FriendRepository friendRepository; public void sendFriendRequest(Long senderId, Long receiverId) { boolean isAlreadySend = friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId); @@ -37,4 +40,34 @@ public void sendFriendRequest(Long senderId, Long receiverId) { .build()); } + public void acceptFriendRequest(Long requestId, Long memberId) { + FriendRequest friendRequest = findAndValidateFriendRequest(requestId, memberId); + + Friend friend1 = createFriend(friendRequest.getSender(), friendRequest.getReceiver()); + Friend friend2 = createFriend(friendRequest.getReceiver(), friendRequest.getSender()); + + friendRepository.save(friend1); + friendRepository.save(friend2); + + friendRequestRepository.delete(friendRequest); + } + + private FriendRequest findAndValidateFriendRequest(Long requestId, Long memberId) { + FriendRequest friendRequest = friendRequestRepository.findById(requestId) + .orElseThrow(() -> new GlobalException(ErrorCode.FRIEND_REQUEST_NOT_FOUND)); + + if (!friendRequest.getReceiver().getId().equals(memberId)) { + throw new GlobalException(ErrorCode.FRIEND_REQUEST_NOT_FOUND); + } + + return friendRequest; + } + + private Friend createFriend(Member member, Member friend) { + return Friend.builder() + .memberId(member) + .friendId(friend) + .build(); + } + } From 12567c82f9b0f1c936a5ef3b981c8fdd6d072c22 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 17:22:36 +0900 Subject: [PATCH 14/26] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B1=B0=EC=A0=88=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 10 ++++++++++ .../everymoment/service/FriendRequestService.java | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java index 65ce16c..f7975a0 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -40,4 +41,13 @@ public ResponseEntity acceptFriendRequest(@PathVariable Long re .body(SuccessResponse.ok()); } + @DeleteMapping("/api/friend-requests/{requestId}/reject") + public ResponseEntity rejectFriendRequest(@PathVariable Long requestId, + @AuthenticationPrincipal MemberDetails memberDetails) { + friendRequestService.rejectFriendRequest(requestId, memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java index 1a8fa6d..20b3a77 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -52,6 +52,12 @@ public void acceptFriendRequest(Long requestId, Long memberId) { friendRequestRepository.delete(friendRequest); } + public void rejectFriendRequest(Long requestId, Long memberId) { + FriendRequest friendRequest = findAndValidateFriendRequest(requestId, memberId); + + friendRequestRepository.delete(friendRequest); + } + private FriendRequest findAndValidateFriendRequest(Long requestId, Long memberId) { FriendRequest friendRequest = friendRequestRepository.findById(requestId) .orElseThrow(() -> new GlobalException(ErrorCode.FRIEND_REQUEST_NOT_FOUND)); From 6b4c9eab5db438126aba5825f76053acbac08ded Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 18:18:44 +0900 Subject: [PATCH 15/26] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9A=94=EC=B2=AD=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 17 ++++++++- .../response/FriendRequestPageRequest.java | 16 ++++++++ .../dto/response/FriendRequestResponse.java | 13 +++++++ .../repository/FriendRepository.java | 2 + .../repository/FriendRequestRepository.java | 7 ++++ .../service/FriendRequestService.java | 38 +++++++++++++++++++ .../everymoment/service/MemberService.java | 6 ++- .../everymoment/util/IdExtractor.java | 8 ++++ .../everymoment/util/PagingUtil.java | 14 +++---- .../everymoment/util/PagingUtilTest.java | 7 ++-- 10 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java create mode 100644 src/main/java/com/potatocake/everymoment/util/IdExtractor.java diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java index f7975a0..f960fbc 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -1,6 +1,7 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.response.FriendRequestPageRequest; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.security.MemberDetails; @@ -9,8 +10,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @@ -19,6 +22,18 @@ public class FriendRequestController { private final FriendRequestService friendRequestService; + @GetMapping("/api/friend-requests") + public ResponseEntity> getFriendRequests( + @RequestParam(required = false) Long key, + @RequestParam(defaultValue = "10") int size, + @AuthenticationPrincipal MemberDetails memberDetails) { + FriendRequestPageRequest friendRequests = friendRequestService.getFriendRequests(key, size, + memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(friendRequests)); + } + @PostMapping("/api/members/{memberId}/friend-requests") public ResponseEntity sendFriendRequest(@PathVariable Long memberId, @AuthenticationPrincipal MemberDetails memberDetails) { @@ -49,5 +64,5 @@ public ResponseEntity rejectFriendRequest(@PathVariable Long re return ResponseEntity.ok() .body(SuccessResponse.ok()); } - + } diff --git a/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java new file mode 100644 index 0000000..1562918 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java @@ -0,0 +1,16 @@ +package com.potatocake.everymoment.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Builder +@Getter +public class FriendRequestPageRequest { + + private List friendRequests; + private Long next; + +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java new file mode 100644 index 0000000..77f1771 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java @@ -0,0 +1,13 @@ +package com.potatocake.everymoment.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class FriendRequestResponse { + + private Long id; + private Long senderId; + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java b/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java index 5eb96f7..70794c6 100644 --- a/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java @@ -8,7 +8,9 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface FriendRepository extends JpaRepository, JpaSpecificationExecutor { + Optional findByMemberIdAndFriendId(Member member, Member friend); List findAllFriendIdsByMemberId(Member member); + } diff --git a/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java index 0557d7f..154c385 100644 --- a/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java @@ -1,8 +1,15 @@ package com.potatocake.everymoment.repository; import com.potatocake.everymoment.entity.FriendRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Window; import org.springframework.data.jpa.repository.JpaRepository; public interface FriendRequestRepository extends JpaRepository { + boolean existsBySenderIdAndReceiverId(Long senderId, Long receiverId); + + Window findByReceiverId(Long receiverId, ScrollPosition scrollPosition, Pageable pageable); + } diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java index 20b3a77..9aeff97 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -1,5 +1,9 @@ package com.potatocake.everymoment.service; +import static org.springframework.data.domain.Sort.Direction.DESC; + +import com.potatocake.everymoment.dto.response.FriendRequestPageRequest; +import com.potatocake.everymoment.dto.response.FriendRequestResponse; import com.potatocake.everymoment.entity.Friend; import com.potatocake.everymoment.entity.FriendRequest; import com.potatocake.everymoment.entity.Member; @@ -8,7 +12,12 @@ import com.potatocake.everymoment.repository.FriendRepository; import com.potatocake.everymoment.repository.FriendRequestRepository; import com.potatocake.everymoment.repository.MemberRepository; +import com.potatocake.everymoment.util.PagingUtil; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Window; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +29,19 @@ public class FriendRequestService { private final FriendRequestRepository friendRequestRepository; private final MemberRepository memberRepository; private final FriendRepository friendRepository; + private final PagingUtil pagingUtil; + + @Transactional(readOnly = true) + public FriendRequestPageRequest getFriendRequests(Long key, int size, Long memberId) { + Window window = fetchFriendRequestWindow(key, size, memberId); + List requests = convertToFriendRequestResponses(window.getContent()); + Long nextKey = pagingUtil.getNextKey(window, FriendRequest::getId); + + return FriendRequestPageRequest.builder() + .friendRequests(requests) + .next(nextKey) + .build(); + } public void sendFriendRequest(Long senderId, Long receiverId) { boolean isAlreadySend = friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId); @@ -76,4 +98,20 @@ private Friend createFriend(Member member, Member friend) { .build(); } + private Window fetchFriendRequestWindow(Long key, int size, Long receiverId) { + ScrollPosition scrollPosition = pagingUtil.createScrollPosition(key); + Pageable pageable = pagingUtil.createPageable(size, DESC); + + return friendRequestRepository.findByReceiverId(receiverId, scrollPosition, pageable); + } + + private List convertToFriendRequestResponses(List requests) { + return requests.stream() + .map(request -> FriendRequestResponse.builder() + .id(request.getId()) + .senderId(request.getSender().getId()) + .build()) + .toList(); + } + } diff --git a/src/main/java/com/potatocake/everymoment/service/MemberService.java b/src/main/java/com/potatocake/everymoment/service/MemberService.java index 11b2db6..f93bfef 100644 --- a/src/main/java/com/potatocake/everymoment/service/MemberService.java +++ b/src/main/java/com/potatocake/everymoment/service/MemberService.java @@ -1,5 +1,7 @@ package com.potatocake.everymoment.service; +import static org.springframework.data.domain.Sort.Direction.ASC; + import com.potatocake.everymoment.dto.response.MemberDetailResponse; import com.potatocake.everymoment.dto.response.MemberResponse; import com.potatocake.everymoment.dto.response.MemberSearchResponse; @@ -32,7 +34,7 @@ public class MemberService { public MemberSearchResponse searchMembers(String nickname, String email, Long key, int size) { Window window = fetchMemberWindow(nickname, email, key, size); List members = convertToMemberResponses(window.getContent()); - Long nextKey = pagingUtil.getNextKey(window); + Long nextKey = pagingUtil.getNextKey(window, Member::getId); return MemberSearchResponse.builder() .members(members) @@ -76,7 +78,7 @@ public void updateMemberInfo(Long id, MultipartFile profileImage, String nicknam private Window fetchMemberWindow(String nickname, String email, Long key, int size) { ScrollPosition scrollPosition = pagingUtil.createScrollPosition(key); - Pageable pageable = pagingUtil.createPageable(size); + Pageable pageable = pagingUtil.createPageable(size, ASC); String searchNickname = (nickname == null) ? "" : nickname; String searchEmail = (email == null) ? "" : email; diff --git a/src/main/java/com/potatocake/everymoment/util/IdExtractor.java b/src/main/java/com/potatocake/everymoment/util/IdExtractor.java new file mode 100644 index 0000000..b2c6c2a --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/util/IdExtractor.java @@ -0,0 +1,8 @@ +package com.potatocake.everymoment.util; + +@FunctionalInterface +public interface IdExtractor { + + Long extractId(T item); + +} diff --git a/src/main/java/com/potatocake/everymoment/util/PagingUtil.java b/src/main/java/com/potatocake/everymoment/util/PagingUtil.java index 90c5df3..1c33922 100644 --- a/src/main/java/com/potatocake/everymoment/util/PagingUtil.java +++ b/src/main/java/com/potatocake/everymoment/util/PagingUtil.java @@ -1,6 +1,5 @@ package com.potatocake.everymoment.util; -import com.potatocake.everymoment.entity.Member; import java.util.Map; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -16,14 +15,15 @@ public ScrollPosition createScrollPosition(Long key) { return key == null ? ScrollPosition.offset() : ScrollPosition.forward(Map.of("id", key)); } - public Pageable createPageable(int size) { - return PageRequest.of(0, size, Sort.by(Sort.Direction.ASC, "id")); + public Pageable createPageable(int size, Sort.Direction direction) { + return PageRequest.of(0, size, Sort.by(direction, "id")); } - public Long getNextKey(Window window) { - return window.hasNext() - ? ((Member) window.getContent().get(window.getContent().size() - 1)).getId() - : null; + public Long getNextKey(Window window, IdExtractor idExtractor) { + if (!window.hasNext() || window.getContent().isEmpty()) { + return null; + } + return idExtractor.extractId(window.getContent().get(window.getContent().size() - 1)); } } diff --git a/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java b/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java index eb110d9..4808d11 100644 --- a/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java +++ b/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java @@ -1,6 +1,7 @@ package com.potatocake.everymoment.util; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.domain.Sort.Direction.ASC; import com.potatocake.everymoment.entity.Member; import java.util.List; @@ -35,7 +36,7 @@ void should_CreateScrollPosition_When_KeyIsNull() { @DisplayName("페이지 정보가 성공적으로 생성된다.") void should_CreatePageable_When_ValidSizeProvided() { // when - Pageable pageable = pagingUtil.createPageable(10); + Pageable pageable = pagingUtil.createPageable(10, ASC); // then assertThat(pageable).isNotNull(); @@ -50,7 +51,7 @@ void should_ReturnNextKey_When_WindowHasNext() { Window window = Window.from(members, ScrollPosition::offset, true); // when - Long nextKey = pagingUtil.getNextKey(window); + Long nextKey = pagingUtil.getNextKey(window, Member::getId); // then assertThat(nextKey).isNotNull(); @@ -65,7 +66,7 @@ void should_ReturnNull_When_WindowHasNoNext() { Window window = Window.from(members, ScrollPosition::offset, false); // when - Long nextKey = pagingUtil.getNextKey(window); + Long nextKey = pagingUtil.getNextKey(window, Member::getId); // then assertThat(nextKey).isNull(); From 312b235152bd208937604d6f2d138e956471805e Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 18:37:43 +0900 Subject: [PATCH 16/26] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80/=EC=82=AD=EC=A0=9C=20(=ED=86=A0=EA=B8=80)=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LikeController.java | 30 ++++++++++++ .../potatocake/everymoment/entity/Like.java | 36 ++++++++++++++ .../repository/LikeRepository.java | 11 +++++ .../everymoment/service/LikeService.java | 47 +++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/controller/LikeController.java create mode 100644 src/main/java/com/potatocake/everymoment/entity/Like.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/LikeRepository.java create mode 100644 src/main/java/com/potatocake/everymoment/service/LikeService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/LikeController.java b/src/main/java/com/potatocake/everymoment/controller/LikeController.java new file mode 100644 index 0000000..739aa72 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/LikeController.java @@ -0,0 +1,30 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.LikeService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/diaries/{diaryId}/likes") +@RestController +public class LikeController { + + private final LikeService likeService; + + @PostMapping + public ResponseEntity toggleLike(@PathVariable Long diaryId, + @AuthenticationPrincipal MemberDetails memberDetails) { + likeService.toggleLike(memberDetails.getId(), diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/entity/Like.java b/src/main/java/com/potatocake/everymoment/entity/Like.java new file mode 100644 index 0000000..8f8da95 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/entity/Like.java @@ -0,0 +1,36 @@ +package com.potatocake.everymoment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Table(name = "likes") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Like { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + private Diary diary; + + @Builder + public Like(Long id, Member member, Diary diary) { + this.id = id; + this.member = member; + this.diary = diary; + } + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java new file mode 100644 index 0000000..c93a17d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java @@ -0,0 +1,11 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.Like; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LikeRepository extends JpaRepository { + + Optional findByMemberIdAndDiaryId(Long memberId, Long diaryId); + +} diff --git a/src/main/java/com/potatocake/everymoment/service/LikeService.java b/src/main/java/com/potatocake/everymoment/service/LikeService.java new file mode 100644 index 0000000..e7f3894 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/LikeService.java @@ -0,0 +1,47 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Like; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.LikeRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class LikeService { + + private final LikeRepository likeRepository; + private final DiaryRepository diaryRepository; + private final MemberRepository memberRepository; + + public void toggleLike(Long memberId, Long diaryId) { + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Optional existingLike = likeRepository.findByMemberIdAndDiaryId(memberId, diaryId); + + if (existingLike.isPresent()) { + // 이미 좋아요가 존재하면 삭제 (좋아요 취소) + likeRepository.delete(existingLike.get()); + } else { + // 좋아요가 없으면 새로 생성 (좋아요 추가) + Like likeEntity = Like.builder() + .diary(diary) + .member(member) + .build(); + + likeRepository.save(likeEntity); + } + } + +} From 92bf07992970e1680b1c52466d2fbf48fc251f3a Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 18:46:28 +0900 Subject: [PATCH 17/26] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=EC=9D=BC?= =?UTF-8?q?=EA=B8=B0=EC=9D=98=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=88=98=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/LikeController.java | 10 ++++++++++ .../everymoment/dto/response/LikeCountResponse.java | 12 ++++++++++++ .../everymoment/repository/LikeRepository.java | 3 +++ .../potatocake/everymoment/service/LikeService.java | 13 +++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java diff --git a/src/main/java/com/potatocake/everymoment/controller/LikeController.java b/src/main/java/com/potatocake/everymoment/controller/LikeController.java index 739aa72..229497e 100644 --- a/src/main/java/com/potatocake/everymoment/controller/LikeController.java +++ b/src/main/java/com/potatocake/everymoment/controller/LikeController.java @@ -1,11 +1,13 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.response.LikeCountResponse; import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.LikeService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RequestMapping; @@ -18,6 +20,14 @@ public class LikeController { private final LikeService likeService; + @GetMapping + public ResponseEntity> getLikeCount(@PathVariable Long diaryId) { + LikeCountResponse likeCount = likeService.getLikeCount(diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(likeCount)); + } + @PostMapping public ResponseEntity toggleLike(@PathVariable Long diaryId, @AuthenticationPrincipal MemberDetails memberDetails) { diff --git a/src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java new file mode 100644 index 0000000..0db2204 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java @@ -0,0 +1,12 @@ +package com.potatocake.everymoment.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class LikeCountResponse { + + private Long likeCount; + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java index c93a17d..dc383b1 100644 --- a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java @@ -1,5 +1,6 @@ package com.potatocake.everymoment.repository; +import com.potatocake.everymoment.entity.Diary; import com.potatocake.everymoment.entity.Like; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,4 +9,6 @@ public interface LikeRepository extends JpaRepository { Optional findByMemberIdAndDiaryId(Long memberId, Long diaryId); + Long countByDiary(Diary diary); + } diff --git a/src/main/java/com/potatocake/everymoment/service/LikeService.java b/src/main/java/com/potatocake/everymoment/service/LikeService.java index e7f3894..5e9bc3d 100644 --- a/src/main/java/com/potatocake/everymoment/service/LikeService.java +++ b/src/main/java/com/potatocake/everymoment/service/LikeService.java @@ -1,5 +1,6 @@ package com.potatocake.everymoment.service; +import com.potatocake.everymoment.dto.response.LikeCountResponse; import com.potatocake.everymoment.entity.Diary; import com.potatocake.everymoment.entity.Like; import com.potatocake.everymoment.entity.Member; @@ -22,6 +23,18 @@ public class LikeService { private final DiaryRepository diaryRepository; private final MemberRepository memberRepository; + @Transactional(readOnly = true) + public LikeCountResponse getLikeCount(Long diaryId) { + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + + Long likeCount = likeRepository.countByDiary(diary); + + return LikeCountResponse.builder() + .likeCount(likeCount) + .build(); + } + public void toggleLike(Long memberId, Long diaryId) { Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); From c198766cff2bf59b8cb700771446962fc3902129 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Thu, 3 Oct 2024 17:27:48 +0900 Subject: [PATCH 18/26] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=8F=20=ED=9A=8C=EC=9B=90=20=EA=B2=80=EC=83=89=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 시 이메일 대신 회원 번호 사용 - 닉네임으로만 회원 검색 가능 (기존: 닉네임과 이메일) --- .../controller/FriendController.java | 3 +- .../controller/MemberController.java | 5 +-- .../dto/request/MemberLoginRequest.java | 2 +- .../dto/response/MemberDetailResponse.java | 1 - .../potatocake/everymoment/entity/Member.java | 4 +- .../exception/ValidationErrorMessage.java | 9 ----- .../repository/MemberRepository.java | 7 ++-- .../security/MemberAuthenticationService.java | 4 +- .../everymoment/security/MemberDetails.java | 2 +- .../security/filter/LoginFilter.java | 10 +++-- .../everymoment/service/FriendService.java | 11 ++--- .../service/FriendSpecification.java | 7 +--- .../everymoment/service/MemberService.java | 11 ++--- .../controller/MemberControllerTest.java | 18 ++++----- .../repository/MemberRepositoryTest.java | 40 +++++++++---------- .../service/MemberServiceTest.java | 7 ++-- 16 files changed, 59 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendController.java b/src/main/java/com/potatocake/everymoment/controller/FriendController.java index d108739..8fb3f75 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendController.java @@ -45,10 +45,9 @@ public ResponseEntity> getOneFriendDia @GetMapping("/friends") public ResponseEntity> getFriendList( @RequestParam(required = false) String nickname, - @RequestParam(required = false) String email, @RequestParam(defaultValue = "0") int key, @RequestParam(defaultValue = "10") int size) { - FriendListResponse friendList = friendService.getFriendList(nickname, email, key, size); + FriendListResponse friendList = friendService.getFriendList(nickname, key, size); SuccessResponse response = SuccessResponse.builder() .code(HttpStatus.OK.value()) .message("success") diff --git a/src/main/java/com/potatocake/everymoment/controller/MemberController.java b/src/main/java/com/potatocake/everymoment/controller/MemberController.java index 3d255dc..55f2fa4 100644 --- a/src/main/java/com/potatocake/everymoment/controller/MemberController.java +++ b/src/main/java/com/potatocake/everymoment/controller/MemberController.java @@ -32,7 +32,7 @@ public class MemberController { private final MemberService memberService; - @Operation(summary = "로그인", description = "이메일과 닉네임으로 로그인합니다.") + @Operation(summary = "로그인", description = "회원 번호와 닉네임으로 로그인합니다.") // @ApiResponses(value = { // @ApiResponse(responseCode = "200", description = "로그인 성공", // content = @Content(schema = @Schema(implementation = JwtResponse.class))), @@ -49,11 +49,10 @@ public ResponseEntity> login(@RequestBody MemberLog @GetMapping public ResponseEntity> searchMembers( @RequestParam(required = false) String nickname, - @RequestParam(required = false) String email, @RequestParam(required = false) Long key, @RequestParam(defaultValue = "10") int size) { - MemberSearchResponse response = memberService.searchMembers(nickname, email, key, size); + MemberSearchResponse response = memberService.searchMembers(nickname, key, size); return ResponseEntity.ok() .body(SuccessResponse.ok(response)); diff --git a/src/main/java/com/potatocake/everymoment/dto/request/MemberLoginRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/MemberLoginRequest.java index 005375b..f59d229 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/MemberLoginRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/MemberLoginRequest.java @@ -5,7 +5,7 @@ @Getter public class MemberLoginRequest { - private String email; + private Long number; private String nickname; } diff --git a/src/main/java/com/potatocake/everymoment/dto/response/MemberDetailResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/MemberDetailResponse.java index 0180208..9abbac6 100644 --- a/src/main/java/com/potatocake/everymoment/dto/response/MemberDetailResponse.java +++ b/src/main/java/com/potatocake/everymoment/dto/response/MemberDetailResponse.java @@ -10,6 +10,5 @@ public class MemberDetailResponse { private Long id; private String profileImageUrl; private String nickname; - private String email; } diff --git a/src/main/java/com/potatocake/everymoment/entity/Member.java b/src/main/java/com/potatocake/everymoment/entity/Member.java index fa657f4..94e9c11 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Member.java +++ b/src/main/java/com/potatocake/everymoment/entity/Member.java @@ -23,8 +23,8 @@ public class Member extends BaseTimeEntity { @Id private Long id; - @Column(nullable = false, unique = true, length = 50) - private String email; + @Column(nullable = false, unique = true) + private Long number; @Column(nullable = false, length = 50) private String nickname; diff --git a/src/main/java/com/potatocake/everymoment/exception/ValidationErrorMessage.java b/src/main/java/com/potatocake/everymoment/exception/ValidationErrorMessage.java index 695c825..71dcdf7 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ValidationErrorMessage.java +++ b/src/main/java/com/potatocake/everymoment/exception/ValidationErrorMessage.java @@ -4,13 +4,4 @@ public class ValidationErrorMessage { public static final String VALIDATION_ERROR = "입력 데이터의 유효성을 검사하던 중 문제가 발생했습니다."; - public static final String EMAIL_FORMAT_INVALID = "올바른 이메일 형식을 입력해 주세요."; - public static final String EMAIL_NOT_BLANK = "이메일은 필수 입력 값입니다."; - public static final String EMAIL_SIZE_INVALID = "이메일은 50자 이하여야 합니다."; - - public static final String NICKNAME_NOT_BLANK = "닉네임을 입력해 주세요."; - public static final String NICKNAME_SIZE_INVALID = "닉네임은 50자 이하여야 합니다."; - - public static final String CATEGORY_NAME_NOT_BLANK = "카테고리명을 입력해 주세요."; - } diff --git a/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java b/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java index 03b501c..135e588 100644 --- a/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/MemberRepository.java @@ -9,11 +9,10 @@ public interface MemberRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByNumber(Long number); - boolean existsByEmail(String email); + boolean existsByNumber(Long number); - Window findByNicknameContainingAndEmailContaining(String nickname, String email, ScrollPosition position, - Pageable pageable); + Window findByNicknameContaining(String nickname, ScrollPosition position, Pageable pageable); } diff --git a/src/main/java/com/potatocake/everymoment/security/MemberAuthenticationService.java b/src/main/java/com/potatocake/everymoment/security/MemberAuthenticationService.java index c788d24..340e981 100644 --- a/src/main/java/com/potatocake/everymoment/security/MemberAuthenticationService.java +++ b/src/main/java/com/potatocake/everymoment/security/MemberAuthenticationService.java @@ -16,8 +16,8 @@ public class MemberAuthenticationService implements UserDetailsService { private final MemberRepository memberRepository; @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - Member member = memberRepository.findByEmail(email) + public UserDetails loadUserByUsername(String number) throws UsernameNotFoundException { + Member member = memberRepository.findByNumber(Long.valueOf(number)) .orElseThrow(() -> new UsernameNotFoundException(ErrorCode.LOGIN_FAILED.getMessage())); return new MemberDetails(member); diff --git a/src/main/java/com/potatocake/everymoment/security/MemberDetails.java b/src/main/java/com/potatocake/everymoment/security/MemberDetails.java index c93d8e0..b4fedca 100644 --- a/src/main/java/com/potatocake/everymoment/security/MemberDetails.java +++ b/src/main/java/com/potatocake/everymoment/security/MemberDetails.java @@ -31,7 +31,7 @@ public String getPassword() { @Override public String getUsername() { - return member.getEmail(); + return String.valueOf(member.getNumber()); } public Long getId() { diff --git a/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java b/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java index 18760f8..53a2951 100644 --- a/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java +++ b/src/main/java/com/potatocake/everymoment/security/filter/LoginFilter.java @@ -53,12 +53,14 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ MemberLoginRequest loginRequest = getLoginRequest(request); - if (loginRequest.getEmail() != null && loginRequest.getNickname() != null) { + if (loginRequest.getNumber() == null || loginRequest.getNickname() == null) { + throw new AuthenticationServiceException("Invalid login request"); + } else { registerIfNotExists(loginRequest); } UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated( - loginRequest.getEmail(), DEFAULT_PASSWORD); + String.valueOf(loginRequest.getNumber()), DEFAULT_PASSWORD); return authenticationManager.authenticate(authRequest); } @@ -117,14 +119,14 @@ private ErrorResponse getErrorResponse(ErrorCode loginFailed) { } private void registerIfNotExists(MemberLoginRequest loginRequest) { - if (!memberRepository.existsByEmail(loginRequest.getEmail())) { + if (!memberRepository.existsByNumber(loginRequest.getNumber())) { memberRepository.save(getMember(loginRequest)); } } private Member getMember(MemberLoginRequest loginRequest) { Member member = Member.builder() - .email(loginRequest.getEmail()) + .number(loginRequest.getNumber()) .nickname(loginRequest.getNickname()) .build(); diff --git a/src/main/java/com/potatocake/everymoment/service/FriendService.java b/src/main/java/com/potatocake/everymoment/service/FriendService.java index d252170..ba78771 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendService.java @@ -51,8 +51,9 @@ public OneFriendDiariesResponse OneFriendDiariesResponse(Long id, LocalDate date Pageable pageable = PageRequest.of(key, size); - Page diaries = diaryRepository.findAll(DiarySpecification.filterDiaries(null, null, date, null, null, null) - .and((root, query, builder) -> builder.equal(root.get("memberId").get("id"), id)), pageable); + Page diaries = diaryRepository.findAll( + DiarySpecification.filterDiaries(null, null, date, null, null, null) + .and((root, query, builder) -> builder.equal(root.get("memberId").get("id"), id)), pageable); List diaryList = diaries.getContent().stream() .map(this::convertToFriendDiariesResponseDTO) @@ -68,7 +69,7 @@ public OneFriendDiariesResponse OneFriendDiariesResponse(Long id, LocalDate date //내 친구 목록 조회 @Transactional(readOnly = true) - public FriendListResponse getFriendList(String nickname, String email, int key, int size) { + public FriendListResponse getFriendList(String nickname, int key, int size) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); Member currentMember = memberDetails.getMember(); @@ -76,7 +77,7 @@ public FriendListResponse getFriendList(String nickname, String email, int key, Pageable pageable = PageRequest.of(key, size); - Specification spec = FriendSpecification.filterFriends(memberId, nickname, email) + Specification spec = FriendSpecification.filterFriends(memberId, nickname) .and((root, query, builder) -> builder.equal(root.get("memberId").get("id"), memberId)); Page friends = friendRepository.findAll(spec, pageable); @@ -147,7 +148,7 @@ private FriendDiarySimpleResponse convertToFriendDiariesResponseDTO(Diary savedD } //친구 프로필 DTO 변환 - private FriendProfileResponse convertToFriendProfileResponseDTO(Friend friend){ + private FriendProfileResponse convertToFriendProfileResponseDTO(Friend friend) { Member friendMember = friend.getFriendId(); return FriendProfileResponse.builder() .id(friendMember.getId()) diff --git a/src/main/java/com/potatocake/everymoment/service/FriendSpecification.java b/src/main/java/com/potatocake/everymoment/service/FriendSpecification.java index 6d193f1..71b9451 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendSpecification.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendSpecification.java @@ -10,7 +10,7 @@ import org.springframework.data.jpa.domain.Specification; public class FriendSpecification { - public static Specification filterFriends(Long memberId, String nickname, String email) { + public static Specification filterFriends(Long memberId, String nickname) { return (Root root, CriteriaQuery query, CriteriaBuilder builder) -> { Predicate predicate = builder.conjunction(); @@ -21,11 +21,6 @@ public static Specification filterFriends(Long memberId, String nickname predicate = builder.and(predicate, builder.like(friendJoin.get("nickname"), "%" + nickname + "%")); } - if (email != null) { - Join friendJoin = root.join("friendId"); - predicate = builder.and(predicate, builder.like(friendJoin.get("email"), "%" + email + "%")); - } - return predicate; }; } diff --git a/src/main/java/com/potatocake/everymoment/service/MemberService.java b/src/main/java/com/potatocake/everymoment/service/MemberService.java index f93bfef..680acea 100644 --- a/src/main/java/com/potatocake/everymoment/service/MemberService.java +++ b/src/main/java/com/potatocake/everymoment/service/MemberService.java @@ -31,8 +31,8 @@ public class MemberService { private final S3FileUploader s3FileUploader; @Transactional(readOnly = true) - public MemberSearchResponse searchMembers(String nickname, String email, Long key, int size) { - Window window = fetchMemberWindow(nickname, email, key, size); + public MemberSearchResponse searchMembers(String nickname, Long key, int size) { + Window window = fetchMemberWindow(nickname, key, size); List members = convertToMemberResponses(window.getContent()); Long nextKey = pagingUtil.getNextKey(window, Member::getId); @@ -51,7 +51,6 @@ public MemberDetailResponse getMyInfo(Long memberId) { .id(member.getId()) .profileImageUrl(member.getProfileImageUrl()) .nickname(member.getNickname()) - .email(member.getEmail()) .build(); } @@ -76,15 +75,13 @@ public void updateMemberInfo(Long id, MultipartFile profileImage, String nicknam member.update(nickname, profileImageUrl); } - private Window fetchMemberWindow(String nickname, String email, Long key, int size) { + private Window fetchMemberWindow(String nickname, Long key, int size) { ScrollPosition scrollPosition = pagingUtil.createScrollPosition(key); Pageable pageable = pagingUtil.createPageable(size, ASC); String searchNickname = (nickname == null) ? "" : nickname; - String searchEmail = (email == null) ? "" : email; - return memberRepository.findByNicknameContainingAndEmailContaining(searchNickname, searchEmail, scrollPosition, - pageable); + return memberRepository.findByNicknameContaining(searchNickname, scrollPosition, pageable); } private List convertToMemberResponses(List members) { diff --git a/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java index 02ad1a3..c1a1ad1 100644 --- a/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java +++ b/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java @@ -47,17 +47,15 @@ class MemberControllerTest { void should_SearchMembers_When_ValidInput() throws Exception { // given String nickname = "testUser"; - String email = "test@test.com"; Long key = 1L; int size = 10; MemberSearchResponse response = MemberSearchResponse.builder().build(); - given(memberService.searchMembers(nickname, email, key, size)).willReturn(response); + given(memberService.searchMembers(nickname, key, size)).willReturn(response); // when ResultActions result = mockMvc.perform(get("/api/members") .param("nickname", nickname) - .param("email", email) .param("key", key.toString()) .param("size", String.valueOf(size))); @@ -67,7 +65,7 @@ void should_SearchMembers_When_ValidInput() throws Exception { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.message").value("success")); - then(memberService).should().searchMembers(nickname, email, key, size); + then(memberService).should().searchMembers(nickname, key, size); } @Test @@ -75,7 +73,7 @@ void should_SearchMembers_When_ValidInput() throws Exception { void should_ReturnMyInfo_When_ValidMember() throws Exception { // given Long memberId = 1L; - MemberDetails memberDetails = createMemberDetails(memberId, "test@example.com", "test"); + MemberDetails memberDetails = createMemberDetails(memberId, 1234L, "test"); MemberDetailResponse response = MemberDetailResponse.builder().build(); given(memberService.getMyInfo(memberId)).willReturn(response); @@ -120,7 +118,7 @@ void should_ReturnMemberInfo_When_ValidMemberId() throws Exception { void should_UpdateMemberInfo_When_ValidInput() throws Exception { // given Long memberId = 1L; - MemberDetails memberDetails = createMemberDetails(memberId, "test@example.com", "test"); + MemberDetails memberDetails = createMemberDetails(memberId, 1234L, "test"); MockMultipartFile profileImage = new MockMultipartFile("profileImage", "image.png", "image/png", new byte[]{}); String nickname = "newNickname"; @@ -154,16 +152,16 @@ void should_ThrowException_When_ProfileImageAndNicknameAreMissing() throws Excep then(memberService).shouldHaveNoInteractions(); } - private Member createMember(Long memberId, String email, String nickname) { + private Member createMember(Long memberId, Long number, String nickname) { return Member.builder() .id(memberId) - .email(email) + .number(number) .nickname(nickname) .build(); } - private MemberDetails createMemberDetails(Long memberId, String email, String nickname) { - Member member = createMember(memberId, email, nickname); + private MemberDetails createMemberDetails(Long memberId, Long number, String nickname) { + Member member = createMember(memberId, number, nickname); return new MemberDetails(member); } diff --git a/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java index 4cb1bd8..2e72d9e 100644 --- a/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java +++ b/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java @@ -19,58 +19,57 @@ class MemberRepositoryTest { private MemberRepository memberRepository; @Test - @DisplayName("이메일로 회원을 성공적으로 조회한다.") - void should_FindMemberByEmail_When_EmailExists() { + @DisplayName("회원 번호로 회원을 성공적으로 조회한다.") + void should_FindMemberByNumber_When_NumberExists() { // given - String email = "test@test.com"; + Long number = 1234L; memberRepository.save(Member.builder() - .email(email) + .number(number) .nickname("testUser") .build()); // when - Optional foundMember = memberRepository.findByEmail(email); + Optional foundMember = memberRepository.findByNumber(number); // then assertThat(foundMember).isPresent(); - assertThat(foundMember.get().getEmail()).isEqualTo(email); + assertThat(foundMember.get().getNumber()).isEqualTo(number); } @Test - @DisplayName("이메일이 존재하는지 확인한다.") - void should_ReturnTrue_When_EmailExists() { + @DisplayName("회원 번호가 존재하는지 확인한다.") + void should_ReturnTrue_When_NumberExists() { // given - String email = "test@test.com"; + Long number = 1234L; memberRepository.save(Member.builder() - .email(email) + .number(number) .nickname("testUser") .build()); // when - boolean exists = memberRepository.existsByEmail(email); + boolean exists = memberRepository.existsByNumber(number); // then assertThat(exists).isTrue(); } @Test - @DisplayName("닉네임과 이메일을 포함하여 스크롤 방식으로 회원 목록을 조회한다.") - void should_FindByNicknameContainingAndEmailContaining_When_ValidScrollPosition() { + @DisplayName("닉네임을 포함하여 스크롤 방식으로 회원 목록을 조회한다.") + void should_FindByNicknameContaining_When_ValidScrollPosition() { // given String nickname = "test"; - String email = "test"; + Long number = 1234L; for (int i = 1; i <= 15; i++) { memberRepository.save(Member.builder() .nickname(nickname + i) - .email(email + i + "@test.com") + .number(number + i) .build()); } // when ScrollPosition scrollPosition = ScrollPosition.offset(); PageRequest pageRequest = PageRequest.of(0, 10); - Window window = memberRepository.findByNicknameContainingAndEmailContaining(nickname, email, - scrollPosition, pageRequest); + Window window = memberRepository.findByNicknameContaining(nickname, scrollPosition, pageRequest); // then assertThat(window).isNotNull(); @@ -79,15 +78,14 @@ void should_FindByNicknameContainingAndEmailContaining_When_ValidScrollPosition( } @Test - @DisplayName("닉네임과 이메일이 일치하지 않으면 빈 결과를 반환한다.") - void should_ReturnEmpty_When_NoMatchingNicknameAndEmail() { + @DisplayName("닉네임으로 검색 결과가 없다면 빈 결과를 반환한다.") + void should_ReturnEmpty_When_NoMatchingNickname() { // given ScrollPosition scrollPosition = ScrollPosition.offset(); PageRequest pageRequest = PageRequest.of(0, 10); // when - Window window = memberRepository.findByNicknameContainingAndEmailContaining("nonexistent", - "nonexistent@test.com", scrollPosition, pageRequest); + Window window = memberRepository.findByNicknameContaining("nonexistent", scrollPosition, pageRequest); // then assertThat(window.getContent()).isEmpty(); diff --git a/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java b/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java index 3c8e313..5106a5e 100644 --- a/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java +++ b/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java @@ -47,24 +47,23 @@ class MemberServiceTest { void should_ReturnMemberList_When_ValidSearchConditions() { // given String nickname = "testUser"; - String email = "test@test.com"; Long key = 1L; int size = 10; List members = List.of(Member.builder().build()); Window window = Window.from(members, ScrollPosition::offset, false); - given(memberRepository.findByNicknameContainingAndEmailContaining(anyString(), anyString(), any(), any())) + given(memberRepository.findByNicknameContaining(anyString(), any(), any())) .willReturn(window); // when - MemberSearchResponse result = memberService.searchMembers(nickname, email, key, size); + MemberSearchResponse result = memberService.searchMembers(nickname, key, size); // then assertThat(result).isNotNull(); assertThat(result.getMembers()).isNotEmpty(); then(memberRepository).should() - .findByNicknameContainingAndEmailContaining(anyString(), anyString(), any(), any()); + .findByNicknameContaining(anyString(), any(), any()); } @Test From eebc7d94036a5166778bd3f7b20e07e14175884a Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Thu, 3 Oct 2024 17:43:59 +0900 Subject: [PATCH 19/26] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20API=20=EA=B5=AC=ED=98=84=20(soft=20delete)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/MemberController.java | 9 +++++++++ .../java/com/potatocake/everymoment/entity/Member.java | 7 +++++++ .../potatocake/everymoment/service/MemberService.java | 7 +++++++ 3 files changed, 23 insertions(+) diff --git a/src/main/java/com/potatocake/everymoment/controller/MemberController.java b/src/main/java/com/potatocake/everymoment/controller/MemberController.java index 55f2fa4..e680a93 100644 --- a/src/main/java/com/potatocake/everymoment/controller/MemberController.java +++ b/src/main/java/com/potatocake/everymoment/controller/MemberController.java @@ -16,6 +16,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.util.StringUtils; +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; @@ -87,6 +88,14 @@ public ResponseEntity> updateMemberInfo(@AuthenticationPri .body(SuccessResponse.ok()); } + @DeleteMapping + public ResponseEntity> deleteMember(@AuthenticationPrincipal MemberDetails memberDetails) { + memberService.deleteMember(memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + private void validateProfileUpdate(MultipartFile profileImage, String nickname) { if (profileImage == null && !StringUtils.hasText(nickname)) { throw new GlobalException(ErrorCode.INFO_REQUIRED); diff --git a/src/main/java/com/potatocake/everymoment/entity/Member.java b/src/main/java/com/potatocake/everymoment/entity/Member.java index 94e9c11..0142578 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Member.java +++ b/src/main/java/com/potatocake/everymoment/entity/Member.java @@ -11,11 +11,15 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; @Getter @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) +@SQLDelete(sql = "UPDATE member SET deleted = true WHERE id = ?") +@SQLRestriction("deleted = false") @Entity public class Member extends BaseTimeEntity { @@ -32,6 +36,9 @@ public class Member extends BaseTimeEntity { @Lob private String profileImageUrl; + @Column(nullable = false) + private boolean deleted = false; + public void update(String nickname, String profileImageUrl) { this.nickname = nickname; this.profileImageUrl = profileImageUrl; diff --git a/src/main/java/com/potatocake/everymoment/service/MemberService.java b/src/main/java/com/potatocake/everymoment/service/MemberService.java index 680acea..4ac4d15 100644 --- a/src/main/java/com/potatocake/everymoment/service/MemberService.java +++ b/src/main/java/com/potatocake/everymoment/service/MemberService.java @@ -75,6 +75,13 @@ public void updateMemberInfo(Long id, MultipartFile profileImage, String nicknam member.update(nickname, profileImageUrl); } + public void deleteMember(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + memberRepository.delete(member); + } + private Window fetchMemberWindow(String nickname, Long key, int size) { ScrollPosition scrollPosition = pagingUtil.createScrollPosition(key); Pageable pageable = pagingUtil.createPageable(size, ASC); From e97a52a0d4bf3033b18900292604c5952e841334 Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 16:21:06 +0900 Subject: [PATCH 20/26] =?UTF-8?q?refactor:=204=EC=A3=BC=EC=B0=A8=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FriedController /추가 - SuccessResponse 사용 - 로그인 정보 가져오는 방식 수정 --- .../controller/DiaryController.java | 172 +++++++++--------- .../controller/FriendController.java | 75 ++++---- .../everymoment/service/DiaryService.java | 51 +++--- .../service/FriendDiaryService.java | 18 +- .../everymoment/service/FriendService.java | 39 ++-- 5 files changed, 180 insertions(+), 175 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java index e149daa..8b15a82 100644 --- a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java +++ b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java @@ -6,9 +6,11 @@ import com.potatocake.everymoment.dto.request.DiaryManualCreateRequest; import com.potatocake.everymoment.dto.response.FriendDiariesResponse; import com.potatocake.everymoment.dto.response.FriendDiaryResponse; +import com.potatocake.everymoment.dto.response.MemberDetailResponse; import com.potatocake.everymoment.dto.response.MyDiariesResponse; import com.potatocake.everymoment.dto.response.MyDiaryResponse; import com.potatocake.everymoment.dto.response.NotificationResponse; +import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.DiaryService; import com.potatocake.everymoment.service.FriendDiaryService; import java.time.LocalDate; @@ -16,6 +18,7 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -37,32 +40,32 @@ public class DiaryController { //자동 일기 작성 @PostMapping("/auto") public ResponseEntity> createDiaryAuto( + @AuthenticationPrincipal MemberDetails memberDetails, @RequestBody DiaryAutoCreateRequest diaryAutoCreateRequest) { - NotificationResponse notificationResponse = diaryService.createDiaryAuto(diaryAutoCreateRequest); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(notificationResponse) - .build(); - return ResponseEntity.ok(response); + Long memberId = memberDetails.getId(); + + NotificationResponse response = diaryService.createDiaryAuto(memberId, diaryAutoCreateRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } //수기 일기 작성 @PostMapping("/manual") public ResponseEntity> createDiaryManual( + @AuthenticationPrincipal MemberDetails memberDetails, @RequestBody DiaryManualCreateRequest diaryManualCreateRequest) { - diaryService.createDiaryManual(diaryManualCreateRequest); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + Long memberId = memberDetails.getId(); + diaryService.createDiaryManual(memberId, diaryManualCreateRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } //내 일기 전체 조회(타임라인) @GetMapping("/my") public ResponseEntity> getMyDiaries( + @AuthenticationPrincipal MemberDetails memberDetails, @RequestParam(required = false) String keyword, @RequestParam(required = false) String emoji, @RequestParam(required = false) Long category, @@ -73,6 +76,7 @@ public ResponseEntity> getMyDiaries( @RequestParam(defaultValue = "0") int key, @RequestParam(defaultValue = "10") int size ) { + Long memberId = memberDetails.getId(); DiaryFilterRequest diaryFilterRequest = DiaryFilterRequest.builder() .keyword(keyword) .emoji(emoji) @@ -85,79 +89,82 @@ public ResponseEntity> getMyDiaries( .size(size) .build(); - MyDiariesResponse myDiariesResponse = diaryService.getMyDiaries(diaryFilterRequest); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(myDiariesResponse) - .build(); - return ResponseEntity.ok(response); + MyDiariesResponse response = diaryService.getMyDiaries(memberId, diaryFilterRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } //내 일기 상세 조회 - @GetMapping("/my/{id}") - public ResponseEntity> getMyDiary(@PathVariable Long id) { - MyDiaryResponse myDiaryResponse = diaryService.getMyDiary(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(myDiaryResponse) - .build(); - return ResponseEntity.ok(response); + @GetMapping("/my/{diaryId}") + public ResponseEntity> getMyDiary( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId) { + Long memberId = memberDetails.getId(); + + MyDiaryResponse response = diaryService.getMyDiary(memberId, diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } //일기 수정 - @PatchMapping("/{id}") - public ResponseEntity> updateDiary(@PathVariable Long id, - @RequestBody DiaryManualCreateRequest diaryManualCreateRequest) { - diaryService.updateDiary(id, diaryManualCreateRequest); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + @PatchMapping("/{diaryId}") + public ResponseEntity> updateDiary( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId, + @RequestBody DiaryManualCreateRequest diaryManualCreateRequest) { + Long memberId = memberDetails.getId(); + + diaryService.updateDiary(memberId, diaryId, diaryManualCreateRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } //일기 삭제 - @DeleteMapping("/{id}") - public ResponseEntity> deleteDiary(@PathVariable Long id) { - diaryService.deleteDiary(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + @DeleteMapping("/{diaryId}") + public ResponseEntity> deleteDiary( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId) { + Long memberId = memberDetails.getId(); + + diaryService.deleteDiary(memberId, diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } //북마크 설정 토글 - @PatchMapping("/{id}/bookmark") - public ResponseEntity> toggleBookmark(@PathVariable Long id) { - diaryService.toggleBookmark(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + @PatchMapping("/{diaryId}/bookmark") + public ResponseEntity> toggleBookmark( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId) { + Long memberId = memberDetails.getId(); + + diaryService.toggleBookmark(memberId, diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } //공개 설정 토글 - @PatchMapping("/{id}/privacy") - public ResponseEntity> togglePrivacy(@PathVariable Long id) { - diaryService.togglePrivacy(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + @PatchMapping("/{diaryId}/privacy") + public ResponseEntity> togglePrivacy( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId) { + Long memberId = memberDetails.getId(); + + diaryService.togglePrivacy(memberId, diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } //전체 친구 일기 조회 @GetMapping("/friend") public ResponseEntity> getFriendDiaries( + @AuthenticationPrincipal MemberDetails memberDetails, @RequestParam(required = false) String keyword, @RequestParam(required = false) String emoji, @RequestParam(required = false) Long category, @@ -168,6 +175,7 @@ public ResponseEntity> getFriendDiaries( @RequestParam(defaultValue = "0") int key, @RequestParam(defaultValue = "10") int size ) { + Long memberId = memberDetails.getId(); DiaryFilterRequest diaryFilterRequest = DiaryFilterRequest.builder() .keyword(keyword) .emoji(emoji) @@ -180,24 +188,22 @@ public ResponseEntity> getFriendDiaries( .size(size) .build(); - FriendDiariesResponse diaries = friendDiaryService.getFriendDiaries(diaryFilterRequest); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(diaries) - .build(); - return ResponseEntity.ok(response); + FriendDiariesResponse response = friendDiaryService.getFriendDiaries(memberId, diaryFilterRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } //친구 일기 상제 조회 - @GetMapping("/friend/{id}") - public ResponseEntity> getFriendDiary(@PathVariable Long id) { - FriendDiaryResponse diary = friendDiaryService.getFriendDiary(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(diary) - .build(); - return ResponseEntity.ok(response); + @GetMapping("/friend/{diaryId}") + public ResponseEntity> getFriendDiary( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId) { + Long memberId = memberDetails.getId(); + + FriendDiaryResponse response = friendDiaryService.getFriendDiary(memberId, diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } } diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendController.java b/src/main/java/com/potatocake/everymoment/controller/FriendController.java index d108739..bf0b62f 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendController.java @@ -3,11 +3,14 @@ import com.potatocake.everymoment.dto.SuccessResponse; import com.potatocake.everymoment.dto.response.FriendListResponse; import com.potatocake.everymoment.dto.response.OneFriendDiariesResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.FriendService; import java.time.LocalDate; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -17,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("api/friends") +@RequestMapping("/api/friends") public class FriendController { private final FriendService friendService; @@ -26,58 +29,60 @@ public FriendController(FriendService friendService) { } //특정 친구 일기 전체 조회 - @GetMapping("/{id}/diaries") + @GetMapping("/{friendId}/diaries") public ResponseEntity> getOneFriendDiaries( - @PathVariable Long id, + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long friendId, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, @RequestParam(defaultValue = "0") int key, @RequestParam(defaultValue = "10") int size) { - OneFriendDiariesResponse diaries = friendService.OneFriendDiariesResponse(id, date, key, size); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(diaries) - .build(); - return ResponseEntity.ok(response); + Long memberId = memberDetails.getId(); + + OneFriendDiariesResponse response = friendService.OneFriendDiariesResponse(memberId, friendId, date, key, size); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } //내 친구 목록 조회 @GetMapping("/friends") public ResponseEntity> getFriendList( + @AuthenticationPrincipal MemberDetails memberDetails, @RequestParam(required = false) String nickname, @RequestParam(required = false) String email, @RequestParam(defaultValue = "0") int key, @RequestParam(defaultValue = "10") int size) { - FriendListResponse friendList = friendService.getFriendList(nickname, email, key, size); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(friendList) - .build(); - return ResponseEntity.ok(response); + Long memberId = memberDetails.getId(); + + FriendListResponse response = friendService.getFriendList(memberId, nickname, email, key, size); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); } //내 친구 삭제 - @DeleteMapping("/{id}") - public ResponseEntity> deleteFriend(@PathVariable Long id) { - friendService.deleteFriend(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + @DeleteMapping("/{friendId}") + public ResponseEntity> deleteFriend( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long friendId) { + Long memberId = memberDetails.getId(); + + friendService.deleteFriend(memberId, friendId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } //친한 친구 설정 - @PatchMapping("/{id}/bookmark") - public ResponseEntity> toggleCloseFriend(@PathVariable Long id) { - friendService.toggleCloseFriend(id); - SuccessResponse response = SuccessResponse.builder() - .code(HttpStatus.OK.value()) - .message("success") - .info(null) - .build(); - return ResponseEntity.ok(response); + @PatchMapping("/{friendId}/bookmark") + public ResponseEntity> toggleCloseFriend( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long friendId) { + Long memberId = memberDetails.getId(); + + friendService.toggleCloseFriend(memberId, friendId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); } } diff --git a/src/main/java/com/potatocake/everymoment/service/DiaryService.java b/src/main/java/com/potatocake/everymoment/service/DiaryService.java index e8bf635..4ec7848 100644 --- a/src/main/java/com/potatocake/everymoment/service/DiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/DiaryService.java @@ -18,6 +18,7 @@ import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.repository.DiaryCategoryRepository; import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.MemberRepository; import com.potatocake.everymoment.repository.NotificationRepository; import com.potatocake.everymoment.security.MemberDetails; import java.util.ArrayList; @@ -29,6 +30,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -41,13 +43,13 @@ public class DiaryService { private final DiaryRepository diaryRepository; private final DiaryCategoryRepository diaryCategoryRepository; private final NotificationRepository notificationRepository; + private final MemberRepository memberRepository; // 자동 일기 저장 (LocationPoint, Name, Adress 만 저장) - public NotificationResponse createDiaryAuto(DiaryAutoCreateRequest diaryAutoCreateRequest) { + public NotificationResponse createDiaryAuto(Long memberId, DiaryAutoCreateRequest diaryAutoCreateRequest) { // member 가져옴 - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Diary diary = Diary.builder() .memberId(currentMember) @@ -83,11 +85,10 @@ public NotificationResponse createDiaryAuto(DiaryAutoCreateRequest diaryAutoCrea } // 수동 일기 작성 - public void createDiaryManual(DiaryManualCreateRequest diaryManualCreateRequest) { + public void createDiaryManual(Long memberId, DiaryManualCreateRequest diaryManualCreateRequest) { // member Id - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Diary diary = Diary.builder() .memberId(currentMember) @@ -109,11 +110,10 @@ public void createDiaryManual(DiaryManualCreateRequest diaryManualCreateRequest) // 내 일기 전체 조회 (타임라인) @Transactional(readOnly = true) - public MyDiariesResponse getMyDiaries(DiaryFilterRequest diaryFilterRequest) { + public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFilterRequest) { //member 가져옴 - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Page diaryPage; @@ -158,14 +158,14 @@ public MyDiariesResponse getMyDiaries(DiaryFilterRequest diaryFilterRequest) { // 내 일기 상세 조회 @Transactional(readOnly = true) - public MyDiaryResponse getMyDiary(Long id) { - Diary diary = getExistDiary(id); + public MyDiaryResponse getMyDiary(Long memberId, Long diaryId) { + Diary diary = getExistDiary(memberId, diaryId); return convertToMyDiaryResponseDto(diary); } // 내 일기 수정 - public void updateDiary(Long id, DiaryManualCreateRequest diaryManualCreateRequest) { - Diary existingDiary = getExistDiary(id); + public void updateDiary(Long memberId, Long diaryId, DiaryManualCreateRequest diaryManualCreateRequest) { + Diary existingDiary = getExistDiary(memberId, diaryId); //카테고리 업데이트 //파일 업데이트 @@ -182,28 +182,27 @@ public void updateDiary(Long id, DiaryManualCreateRequest diaryManualCreateReque } // 내 일기 삭제 - public void deleteDiary(Long id) { - Diary existingDiary = getExistDiary(id); + public void deleteDiary(Long memberId, Long diaryId) { + Diary existingDiary = getExistDiary(memberId, diaryId); diaryRepository.delete(existingDiary); } // 내 일기 북마크 설정 - public void toggleBookmark(Long id) { - Diary existingDiary = getExistDiary(id); + public void toggleBookmark(Long memberId, Long diaryId) { + Diary existingDiary = getExistDiary(memberId, diaryId); existingDiary.toggleBookmark(); } // 내 일기 공개 설정 - public void togglePrivacy(Long id) { - Diary existingDiary = getExistDiary(id); + public void togglePrivacy(Long memberId, Long diaryId) { + Diary existingDiary = getExistDiary(memberId, diaryId); existingDiary.togglePublic(); } // 로그인한 유저의 일기가 맞는지 확인 후 일기 반환 - private Diary getExistDiary(Long diaryId) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + private Diary getExistDiary(Long memberId, Long diaryId) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); diff --git a/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java b/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java index d6d4ee1..38eca1d 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java @@ -15,6 +15,7 @@ import com.potatocake.everymoment.repository.DiaryCategoryRepository; import com.potatocake.everymoment.repository.DiaryRepository; import com.potatocake.everymoment.repository.FriendRepository; +import com.potatocake.everymoment.repository.MemberRepository; import com.potatocake.everymoment.security.MemberDetails; import java.util.ArrayList; import java.util.List; @@ -35,12 +36,12 @@ public class FriendDiaryService { private final DiaryRepository diaryRepository; private final DiaryCategoryRepository diaryCategoryRepository; private final FriendRepository friendRepository; + private final MemberRepository memberRepository; //친구 일기 조회 - public FriendDiariesResponse getFriendDiaries(DiaryFilterRequest diaryFilterRequest) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + public FriendDiariesResponse getFriendDiaries(Long memberId, DiaryFilterRequest diaryFilterRequest) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); List friends = friendRepository.findAllFriendIdsByMemberId(currentMember); List friendIdList = friends.stream() @@ -87,14 +88,13 @@ public FriendDiariesResponse getFriendDiaries(DiaryFilterRequest diaryFilterRequ } // 친구 다이어리 하나 조회 - public FriendDiaryResponse getFriendDiary(Long id) { - Diary diary = diaryRepository.findById(id) + public FriendDiaryResponse getFriendDiary(Long memberId, Long diaryId) { + Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new IllegalArgumentException("Diary not found")); //글쓴사람이 친구인지 확인 - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); List friends = friendRepository.findAllFriendIdsByMemberId(currentMember); List friendIdList = friends.stream() diff --git a/src/main/java/com/potatocake/everymoment/service/FriendService.java b/src/main/java/com/potatocake/everymoment/service/FriendService.java index d252170..108900d 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendService.java @@ -38,13 +38,12 @@ public class FriendService { //특정 친구 일기 조회 @Transactional(readOnly = true) - public OneFriendDiariesResponse OneFriendDiariesResponse(Long id, LocalDate date, int key, int size) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + public OneFriendDiariesResponse OneFriendDiariesResponse(Long memberid, Long friendId, LocalDate date, int key, int size) { + Member currentMember = memberRepository.findById(memberid) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); //친구인지 확인 - Member friend = memberRepository.findById(id) + Member friend = memberRepository.findById(friendId) .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); friendRepository.findByMemberIdAndFriendId(currentMember, friend) .orElseThrow(() -> new GlobalException(ErrorCode.FRIEND_NOT_FOUND)); @@ -52,7 +51,7 @@ public OneFriendDiariesResponse OneFriendDiariesResponse(Long id, LocalDate date Pageable pageable = PageRequest.of(key, size); Page diaries = diaryRepository.findAll(DiarySpecification.filterDiaries(null, null, date, null, null, null) - .and((root, query, builder) -> builder.equal(root.get("memberId").get("id"), id)), pageable); + .and((root, query, builder) -> builder.equal(root.get("memberId").get("id"), friendId)), pageable); List diaryList = diaries.getContent().stream() .map(this::convertToFriendDiariesResponseDTO) @@ -68,10 +67,9 @@ public OneFriendDiariesResponse OneFriendDiariesResponse(Long id, LocalDate date //내 친구 목록 조회 @Transactional(readOnly = true) - public FriendListResponse getFriendList(String nickname, String email, int key, int size) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); + public FriendListResponse getFriendList(Long memberIdFromController, String nickname, String email, int key, int size) { + Member currentMember = memberRepository.findById(memberIdFromController) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Long memberId = currentMember.getId(); Pageable pageable = PageRequest.of(key, size); @@ -94,12 +92,10 @@ public FriendListResponse getFriendList(String nickname, String email, int key, } // 친구 삭제 - public void deleteFriend(Long id) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); - - Member friendMember = memberRepository.findById(id) + public void deleteFriend(Long memberId, Long firendId) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + Member friendMember = memberRepository.findById(firendId) .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Friend friendMine = friendRepository.findByMemberIdAndFriendId(currentMember, friendMember) @@ -112,13 +108,12 @@ public void deleteFriend(Long id) { } // 친한 친구 설정(토글) - public void toggleCloseFriend(Long id) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); - Member currentMember = memberDetails.getMember(); - - Member friendMember = memberRepository.findById(id) + public void toggleCloseFriend(Long memberId, Long friendId) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + Member friendMember = memberRepository.findById(friendId) .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + Friend friend = friendRepository.findByMemberIdAndFriendId(currentMember, friendMember) .orElseThrow(() -> new GlobalException(ErrorCode.FRIEND_NOT_FOUND)); From 00b7bb6f4ea7a4111beb73346493115bfb0fb870 Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 16:37:31 +0900 Subject: [PATCH 21/26] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0,=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/entity/Comment.java | 43 +++++++++++++++++++ .../repository/CommentRepository.java | 10 +++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/entity/Comment.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/CommentRepository.java diff --git a/src/main/java/com/potatocake/everymoment/entity/Comment.java b/src/main/java/com/potatocake/everymoment/entity/Comment.java new file mode 100644 index 0000000..691cff3 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/entity/Comment.java @@ -0,0 +1,43 @@ +package com.potatocake.everymoment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Getter +@Builder +public class Comment extends BaseTimeEntity{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false) + private Member memberId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false) + private Diary diaryId; + + @Lob + private String content; + + public void updateContent(String content) { + if (content != null) { + this.content = content; + } + } +} diff --git a/src/main/java/com/potatocake/everymoment/repository/CommentRepository.java b/src/main/java/com/potatocake/everymoment/repository/CommentRepository.java new file mode 100644 index 0000000..8129c6d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/CommentRepository.java @@ -0,0 +1,10 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.Comment; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { + Page findAllByDiaryId(Long diaryId, Pageable pageable); +} From 3a750ee98a4699cd9d4e5e0d421be3fd589e727c Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 16:38:28 +0900 Subject: [PATCH 22/26] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1,=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DiaryController.java | 31 +++++++ .../dto/request/CommentRequest.java | 8 ++ .../dto/response/CommentFriendResponse.java | 12 +++ .../dto/response/CommentResponse.java | 14 +++ .../dto/response/CommentsResponse.java | 13 +++ .../everymoment/service/CommentService.java | 86 +++++++++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/CommentFriendResponse.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/CommentResponse.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/CommentsResponse.java create mode 100644 src/main/java/com/potatocake/everymoment/service/CommentService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java index 8b15a82..66047a9 100644 --- a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java +++ b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java @@ -1,9 +1,11 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.request.CommentRequest; import com.potatocake.everymoment.dto.request.DiaryAutoCreateRequest; import com.potatocake.everymoment.dto.request.DiaryFilterRequest; import com.potatocake.everymoment.dto.request.DiaryManualCreateRequest; +import com.potatocake.everymoment.dto.response.CommentsResponse; import com.potatocake.everymoment.dto.response.FriendDiariesResponse; import com.potatocake.everymoment.dto.response.FriendDiaryResponse; import com.potatocake.everymoment.dto.response.MemberDetailResponse; @@ -11,6 +13,7 @@ import com.potatocake.everymoment.dto.response.MyDiaryResponse; import com.potatocake.everymoment.dto.response.NotificationResponse; import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.CommentService; import com.potatocake.everymoment.service.DiaryService; import com.potatocake.everymoment.service.FriendDiaryService; import java.time.LocalDate; @@ -36,6 +39,7 @@ public class DiaryController { private final DiaryService diaryService; private final FriendDiaryService friendDiaryService; + private final CommentService commentService; //자동 일기 작성 @PostMapping("/auto") @@ -206,4 +210,31 @@ public ResponseEntity> getFriendDiary( return ResponseEntity.ok() .body(SuccessResponse.ok(response)); } + + //댓글 조회 + @GetMapping("/{diaryId}/comments") + public ResponseEntity> getComments( + @PathVariable Long diaryId, + @RequestParam(defaultValue = "0") int key, + @RequestParam(defaultValue = "10") int size + ) { + CommentsResponse response = commentService.getComments(diaryId, key, size); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); + } + + //댓글 작성 + @PostMapping("/{diaryId}/comments") + public ResponseEntity> createComment( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long diaryId, + @RequestBody CommentRequest commentRequest) { + Long memberId = memberDetails.getId(); + + commentService.createComment(memberId, diaryId, commentRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } } diff --git a/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java new file mode 100644 index 0000000..db0252a --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java @@ -0,0 +1,8 @@ +package com.potatocake.everymoment.dto.request; + +import lombok.Getter; + +@Getter +public class CommentRequest { + private String content; +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/CommentFriendResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/CommentFriendResponse.java new file mode 100644 index 0000000..b6f09fc --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/CommentFriendResponse.java @@ -0,0 +1,12 @@ +package com.potatocake.everymoment.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CommentFriendResponse { + private Long id; + private String nickname; + private String profileImageUrl; +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/CommentResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/CommentResponse.java new file mode 100644 index 0000000..7fe835d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/CommentResponse.java @@ -0,0 +1,14 @@ +package com.potatocake.everymoment.dto.response; + +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CommentResponse { + private Long id; + private CommentFriendResponse commentFriendResponse; + private String content; + private LocalDateTime createdAt; +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/CommentsResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/CommentsResponse.java new file mode 100644 index 0000000..160b79c --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/CommentsResponse.java @@ -0,0 +1,13 @@ +package com.potatocake.everymoment.dto.response; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CommentsResponse { + private List comments; + private Integer next; +} + diff --git a/src/main/java/com/potatocake/everymoment/service/CommentService.java b/src/main/java/com/potatocake/everymoment/service/CommentService.java new file mode 100644 index 0000000..8f718b2 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/CommentService.java @@ -0,0 +1,86 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.dto.request.CommentRequest; +import com.potatocake.everymoment.dto.response.CommentFriendResponse; +import com.potatocake.everymoment.dto.response.CommentResponse; +import com.potatocake.everymoment.dto.response.CommentsResponse; +import com.potatocake.everymoment.entity.Comment; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.CommentRepository; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class CommentService { + + private final CommentRepository commentRepository; + private final DiaryRepository diaryRepository; + private final MemberRepository memberRepository; + + // 댓글 목록 조회 + public CommentsResponse getComments(Long diaryId, int key, int size) { + Pageable pageable = PageRequest.of(key, size); + Page commentPage = commentRepository.findAllByDiaryId(diaryId, pageable); + + List commentResponses = commentPage.getContent().stream() + .map(this::convertToCommentResponseDTO) + .collect(Collectors.toList()); + + Integer nextKey = commentPage.hasNext() ? key + 1 : null; + + return CommentsResponse.builder() + .comments(commentResponses) + .next(nextKey) + .build(); + } + + // 댓글 작성 + public void createComment(Long memberId, Long diaryId, CommentRequest commentRequest) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + + Comment comment = Comment.builder() + .content(commentRequest.getContent()) + .memberId(currentMember) + .diaryId(diary) + .build(); + + commentRepository.save(comment); + } + + // 친구 프로필 DTO 변환 + private CommentFriendResponse convertToCommentFriendResponseDTO(Member member){ + return CommentFriendResponse.builder() + .id(member.getId()) + .nickname(member.getNickname()) + .profileImageUrl(member.getProfileImageUrl()) + .build(); + } + + // 댓글 DTO 변환 + private CommentResponse convertToCommentResponseDTO(Comment comment){ + return CommentResponse.builder() + .id(comment.getId()) + .commentFriendResponse(convertToCommentFriendResponseDTO(comment.getMemberId())) + .content(comment.getContent()) + .createdAt(comment.getCreateAt()) + .build(); + } +} + From 21278ca8a17889a823a5d8841d096b5b265296ed Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 16:43:20 +0900 Subject: [PATCH 23/26] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommentController.java | 51 +++++++++++++++++++ .../everymoment/service/CommentService.java | 31 +++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/controller/CommentController.java diff --git a/src/main/java/com/potatocake/everymoment/controller/CommentController.java b/src/main/java/com/potatocake/everymoment/controller/CommentController.java new file mode 100644 index 0000000..f462e1d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/CommentController.java @@ -0,0 +1,51 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.request.CommentRequest; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.CommentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/comments") +public class CommentController { + + private final CommentService commentService; + + //댓글 수정 + @PatchMapping("/{commentId}") + public ResponseEntity> updateComment( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long commentId, + @RequestBody CommentRequest commentRequest) { + Long memberId = memberDetails.getId(); + + commentService.updateComment(memberId, commentId, commentRequest); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + + //댓글 삭제 + @DeleteMapping("/{commentId}") + public ResponseEntity> deleteComment( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long commentId) { + Long memberId = memberDetails.getId(); + + commentService.deleteComment(memberId, commentId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } +} diff --git a/src/main/java/com/potatocake/everymoment/service/CommentService.java b/src/main/java/com/potatocake/everymoment/service/CommentService.java index 8f718b2..ac515ae 100644 --- a/src/main/java/com/potatocake/everymoment/service/CommentService.java +++ b/src/main/java/com/potatocake/everymoment/service/CommentService.java @@ -12,12 +12,16 @@ import com.potatocake.everymoment.repository.CommentRepository; import com.potatocake.everymoment.repository.DiaryRepository; import com.potatocake.everymoment.repository.MemberRepository; +import com.potatocake.everymoment.security.MemberDetails; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -64,6 +68,33 @@ public void createComment(Long memberId, Long diaryId, CommentRequest commentReq commentRepository.save(comment); } + // 댓글 수정 + public void updateComment(Long memberId, Long commentId, CommentRequest commentRequest) { + Comment comment = getExistComment(memberId, commentId); + comment.updateContent(commentRequest.getContent()); + } + + // 댓글 삭제 + public void deleteComment(Long memberId, Long commentId) { + Comment comment = getExistComment(memberId, commentId); + commentRepository.delete(comment); + } + + // 로그인한 유저가 쓴 댓글인지 확인하고, 맞을시 댓글 반환 + private Comment getExistComment(Long memberId, Long commentId) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new GlobalException(ErrorCode.COMMENT_NOT_FOUND)); + + if(!Objects.equals(currentMember.getId(), comment.getMemberId().getId())){ + throw new GlobalException(ErrorCode.COMMENT_NOT_FOUND); + } + + return comment; + } + // 친구 프로필 DTO 변환 private CommentFriendResponse convertToCommentFriendResponseDTO(Member member){ return CommentFriendResponse.builder() From a52ecad0a471b4f7e2cce0643cd2e489916b12ee Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 16:54:47 +0900 Subject: [PATCH 24/26] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/NotificationController.java | 46 ++++++++++++++ .../response/NotificationListResponse.java | 17 ++++++ .../everymoment/entity/Notification.java | 4 ++ .../everymoment/exception/ErrorCode.java | 3 + .../repository/NotificationRepository.java | 2 + .../service/NotificationService.java | 61 +++++++++++++++++++ 6 files changed, 133 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/controller/NotificationController.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/NotificationListResponse.java create mode 100644 src/main/java/com/potatocake/everymoment/service/NotificationService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/NotificationController.java b/src/main/java/com/potatocake/everymoment/controller/NotificationController.java new file mode 100644 index 0000000..e8ce3d9 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/NotificationController.java @@ -0,0 +1,46 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.response.NotificationListResponse; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.NotificationService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/notifications") +public class NotificationController { + + private final NotificationService notificationService; + + @GetMapping + public ResponseEntity>> getNotifications( + @AuthenticationPrincipal MemberDetails memberDetails){ + Long memberId = memberDetails.getId(); + + List response = notificationService.getNotifications(memberId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(response)); + } + + @PatchMapping("/{notificationId}") + public ResponseEntity> updateNotification( + @AuthenticationPrincipal MemberDetails memberDetails, + @PathVariable Long notificationId) { + Long memberId = memberDetails.getId(); + + notificationService.updateNotification(memberId, notificationId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/NotificationListResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/NotificationListResponse.java new file mode 100644 index 0000000..f32770e --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/NotificationListResponse.java @@ -0,0 +1,17 @@ +package com.potatocake.everymoment.dto.response; + +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class NotificationListResponse { + private Long id; + private String content; + private boolean isRead; + private String type; + private Long targetId; + private LocalDateTime createdAt; +} + diff --git a/src/main/java/com/potatocake/everymoment/entity/Notification.java b/src/main/java/com/potatocake/everymoment/entity/Notification.java index 7963a15..eace692 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Notification.java +++ b/src/main/java/com/potatocake/everymoment/entity/Notification.java @@ -41,4 +41,8 @@ public class Notification extends BaseTimeEntity { @Column(nullable = false) private Long targetId; + + public void updateIsRead() { + this.isRead = true; + } } diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index f35cd49..9d27ef6 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -23,6 +23,9 @@ public enum ErrorCode { /* Friend */ FRIEND_NOT_FOUND("존재하지 않는 친구입니다.", NOT_FOUND), + /* Notification */ + NOTIFICATION_NOT_FOUND("존재하지 않는 알림입니다.", NOT_FOUND), + /* CategoryService */ ALREADY_EXISTS_CATEGORY("이미 존재하는 카테고리입니다.", CONFLICT), diff --git a/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java b/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java index a3a5974..19297a5 100644 --- a/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/NotificationRepository.java @@ -1,7 +1,9 @@ package com.potatocake.everymoment.repository; import com.potatocake.everymoment.entity.Notification; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface NotificationRepository extends JpaRepository { + List findAllByMemberId(Long memberId); } diff --git a/src/main/java/com/potatocake/everymoment/service/NotificationService.java b/src/main/java/com/potatocake/everymoment/service/NotificationService.java new file mode 100644 index 0000000..270dd3f --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/NotificationService.java @@ -0,0 +1,61 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.dto.response.NotificationListResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.entity.Notification; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.MemberRepository; +import com.potatocake.everymoment.repository.NotificationRepository; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class NotificationService { + + private final NotificationRepository notificationRepository; + private final MemberRepository memberRepository; + + public List getNotifications(Long memberId) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + List notifications = notificationRepository.findAllByMemberId(currentMember.getId()); + + return notifications.stream() + .map(this::convertToNotificationResponseDTO) + .collect(Collectors.toList()); + } + + public void updateNotification(Long memberId, Long notificationId) { + Member currentMember = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Notification notification = notificationRepository.findById(notificationId) + .orElseThrow(() -> new GlobalException(ErrorCode.NOTIFICATION_NOT_FOUND)); + + if(!Objects.equals(currentMember.getId(), notification.getMemberId().getId())){ + throw new GlobalException(ErrorCode.NOTIFICATION_NOT_FOUND); + } + + notification.updateIsRead(); + } + + // 알림 DTO 변환 + private NotificationListResponse convertToNotificationResponseDTO(Notification notification){ + return NotificationListResponse.builder() + .id(notification.getId()) + .content(notification.getContent()) + .isRead(notification.isRead()) + .type(notification.getType()) + .targetId(notification.getTargetId()) + .createdAt(notification.getCreateAt()) + .build(); + } +} From a2442225cfd1096d3fb5b1eb5759654567369103 Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 17:45:00 +0900 Subject: [PATCH 25/26] =?UTF-8?q?fix:=20notification=20Member=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/potatocake/everymoment/entity/Notification.java | 2 +- .../java/com/potatocake/everymoment/service/DiaryService.java | 2 +- .../com/potatocake/everymoment/service/NotificationService.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/entity/Notification.java b/src/main/java/com/potatocake/everymoment/entity/Notification.java index eace692..184ea96 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Notification.java +++ b/src/main/java/com/potatocake/everymoment/entity/Notification.java @@ -27,7 +27,7 @@ public class Notification extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(nullable = false) - private Member memberId; + private Member member; @Column(nullable = false) private String content; diff --git a/src/main/java/com/potatocake/everymoment/service/DiaryService.java b/src/main/java/com/potatocake/everymoment/service/DiaryService.java index 4ec7848..72e1024 100644 --- a/src/main/java/com/potatocake/everymoment/service/DiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/DiaryService.java @@ -64,7 +64,7 @@ public NotificationResponse createDiaryAuto(Long memberId, DiaryAutoCreateReques String content = "현재 " + savedDiary.getLocationName() + "에 머무르고 있어요! 지금 기분은 어떠신가요?"; Notification notification = Notification.builder() - .memberId(currentMember) + .member(currentMember) .content(content) .type("MOOD_CHECK") .targetId(savedDiary.getId()) diff --git a/src/main/java/com/potatocake/everymoment/service/NotificationService.java b/src/main/java/com/potatocake/everymoment/service/NotificationService.java index 270dd3f..e199a24 100644 --- a/src/main/java/com/potatocake/everymoment/service/NotificationService.java +++ b/src/main/java/com/potatocake/everymoment/service/NotificationService.java @@ -40,7 +40,7 @@ public void updateNotification(Long memberId, Long notificationId) { Notification notification = notificationRepository.findById(notificationId) .orElseThrow(() -> new GlobalException(ErrorCode.NOTIFICATION_NOT_FOUND)); - if(!Objects.equals(currentMember.getId(), notification.getMemberId().getId())){ + if(!Objects.equals(currentMember.getId(), notification.getMember().getId())){ throw new GlobalException(ErrorCode.NOTIFICATION_NOT_FOUND); } From 18d218a6bc563a0ddccee60b079cd9b32331ddf9 Mon Sep 17 00:00:00 2001 From: HyeJiJUN Date: Fri, 4 Oct 2024 17:59:47 +0900 Subject: [PATCH 26/26] =?UTF-8?q?fix:=20comment,=20diary=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=ED=81=B4=EB=9F=BC=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/potatocake/everymoment/entity/Comment.java | 4 ++-- .../java/com/potatocake/everymoment/entity/Diary.java | 4 ++-- .../potatocake/everymoment/service/CommentService.java | 8 ++++---- .../com/potatocake/everymoment/service/DiaryService.java | 8 ++++---- .../everymoment/service/FriendDiaryService.java | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/entity/Comment.java b/src/main/java/com/potatocake/everymoment/entity/Comment.java index 691cff3..5024398 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Comment.java +++ b/src/main/java/com/potatocake/everymoment/entity/Comment.java @@ -26,11 +26,11 @@ public class Comment extends BaseTimeEntity{ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(nullable = false) - private Member memberId; + private Member member; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(nullable = false) - private Diary diaryId; + private Diary diary; @Lob private String content; diff --git a/src/main/java/com/potatocake/everymoment/entity/Diary.java b/src/main/java/com/potatocake/everymoment/entity/Diary.java index a0ae427..a8abca1 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Diary.java +++ b/src/main/java/com/potatocake/everymoment/entity/Diary.java @@ -27,7 +27,7 @@ public class Diary extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(nullable = false) - private Member memberId; + private Member member; @Lob private String content; @@ -100,7 +100,7 @@ public void togglePublic() { } public boolean checkOwner(Long memberId) { - return this.memberId.getId().equals(memberId); + return this.member.getId().equals(memberId); } } diff --git a/src/main/java/com/potatocake/everymoment/service/CommentService.java b/src/main/java/com/potatocake/everymoment/service/CommentService.java index ac515ae..b0d7663 100644 --- a/src/main/java/com/potatocake/everymoment/service/CommentService.java +++ b/src/main/java/com/potatocake/everymoment/service/CommentService.java @@ -61,8 +61,8 @@ public void createComment(Long memberId, Long diaryId, CommentRequest commentReq Comment comment = Comment.builder() .content(commentRequest.getContent()) - .memberId(currentMember) - .diaryId(diary) + .member(currentMember) + .diary(diary) .build(); commentRepository.save(comment); @@ -88,7 +88,7 @@ private Comment getExistComment(Long memberId, Long commentId) { Comment comment = commentRepository.findById(commentId) .orElseThrow(() -> new GlobalException(ErrorCode.COMMENT_NOT_FOUND)); - if(!Objects.equals(currentMember.getId(), comment.getMemberId().getId())){ + if(!Objects.equals(currentMember.getId(), comment.getMember().getId())){ throw new GlobalException(ErrorCode.COMMENT_NOT_FOUND); } @@ -108,7 +108,7 @@ private CommentFriendResponse convertToCommentFriendResponseDTO(Member member){ private CommentResponse convertToCommentResponseDTO(Comment comment){ return CommentResponse.builder() .id(comment.getId()) - .commentFriendResponse(convertToCommentFriendResponseDTO(comment.getMemberId())) + .commentFriendResponse(convertToCommentFriendResponseDTO(comment.getMember())) .content(comment.getContent()) .createdAt(comment.getCreateAt()) .build(); diff --git a/src/main/java/com/potatocake/everymoment/service/DiaryService.java b/src/main/java/com/potatocake/everymoment/service/DiaryService.java index 72e1024..8debdc8 100644 --- a/src/main/java/com/potatocake/everymoment/service/DiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/DiaryService.java @@ -52,7 +52,7 @@ public NotificationResponse createDiaryAuto(Long memberId, DiaryAutoCreateReques .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Diary diary = Diary.builder() - .memberId(currentMember) + .member(currentMember) .locationPoint(diaryAutoCreateRequest.getLocationPoint().toString()) .locationName(diaryAutoCreateRequest.getLocationName()) .address(diaryAutoCreateRequest.getAddress()) @@ -91,7 +91,7 @@ public void createDiaryManual(Long memberId, DiaryManualCreateRequest diaryManua .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); Diary diary = Diary.builder() - .memberId(currentMember) + .member(currentMember) .content(diaryManualCreateRequest.getContent()) .locationPoint(diaryManualCreateRequest.getLocationPoint().toString()) .locationName(diaryManualCreateRequest.getLocationName()) @@ -133,7 +133,7 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil // Diary 중에 memberId같은 것 가져옴 List DiaryIdList = diaryCategoryList.stream() - .filter(diaryCategory -> diaryCategory.getDiary().getMemberId() + .filter(diaryCategory -> diaryCategory.getDiary().getMember() .equals(currentMember)) // memberId가 일치하는 경우 필터링 .map(diaryCategory -> diaryCategory.getDiary().getId()) .collect(Collectors.toList()); @@ -207,7 +207,7 @@ private Diary getExistDiary(Long memberId, Long diaryId) { Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); - if (!Objects.equals(currentMember.getId(), diary.getMemberId().getId())) { + if (!Objects.equals(currentMember.getId(), diary.getMember().getId())) { throw new GlobalException(ErrorCode.DIARY_NOT_FOUND); } diff --git a/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java b/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java index 38eca1d..7a8fa74 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java @@ -64,7 +64,7 @@ public FriendDiariesResponse getFriendDiaries(Long memberId, DiaryFilterRequest // Diary중에 memberId같은 것 가져옴 List filteredDiaryIds = diaryCategories.stream() - .filter(diaryCategory -> friendIdList.contains(diaryCategory.getDiary().getMemberId())) // memberIds 목록에서 필터링 + .filter(diaryCategory -> friendIdList.contains(diaryCategory.getDiary().getMember())) // memberIds 목록에서 필터링 .map(diaryCategory -> diaryCategory.getDiary().getId()) .collect(Collectors.toList()); @@ -101,7 +101,7 @@ public FriendDiaryResponse getFriendDiary(Long memberId, Long diaryId) { .map(Member::getId) .collect(Collectors.toList()); - if(!friendIdList.contains(diary.getMemberId())){ + if(!friendIdList.contains(diary.getMember())){ throw new GlobalException(ErrorCode.FRIEND_NOT_FOUND); } //카테고리 찾음