From 1d26dd1f0eb300fdf718cb280e2aea0abd80323e Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Fri, 25 Oct 2024 09:53:20 +0900 Subject: [PATCH 01/24] :memo: docs: kubernetes --- README.md | 1 + docs/kubernetes.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 docs/kubernetes.md diff --git a/README.md b/README.md index 0c11ba4..ba943dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ## Docs - [docker-compose](docs/docker-compose.md) +- [kubernetes](docs/kubernetes.md) - [package-structure](docs/package-structure.md) diff --git a/docs/kubernetes.md b/docs/kubernetes.md new file mode 100644 index 0000000..041a89a --- /dev/null +++ b/docs/kubernetes.md @@ -0,0 +1,91 @@ +## Run + +```bash +$ kubectl create ns co-co-gong +namespace/co-co-gong created +$ kubectl apply -n co-co-gong -f k8s +deployment.apps/backend created +service/backend created +ingressroute.traefik.io/co-co-gong created +deployment.apps/postgres created +service/postgres created +configmap/postgres-config created +secret/postgres-secret created +secret/oauth-secret created +secret/jwt-secret created +$ kubectl get all -n co-co-gong +NAME READY STATUS RESTARTS AGE +pod/backend-7ff75486b8-dtmh2 1/1 Running 0 3m39s +pod/postgres-58585c55b4-7vsrs 1/1 Running 0 3m39s + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/backend ClusterIP 10.99.127.11 8080/TCP 3m39s +service/postgres ClusterIP 10.109.182.198 5432/TCP 3m39s + +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/backend 1/1 1 1 3m39s +deployment.apps/postgres 1/1 1 1 3m39s + +NAME DESIRED CURRENT READY AGE +replicaset.apps/backend-7ff75486b8 1 1 1 3m39s +replicaset.apps/postgres-58585c55b4 1 1 1 3m39s +``` + +## Logs + +```bash +$ kubectl logs -n co-co-gong deploy/backend +Downloading https://services.gradle.org/distributions/gradle-8.8-bin.zip +.............10%.............20%.............30%.............40%.............50%.............60%..............70%.............80%.............90%........... +..100% + +Welcome to Gradle 8.8! + +Here are the highlights of this release: + - Running Gradle on Java 22 + - Configurable Gradle daemon JVM + - Improved IDE performance for large projects + +For more details see https://docs.gradle.org/8.8/release-notes.html + +Starting a Gradle Daemon (subsequent builds will be faster) +> Task :compileJava +> Task :processResources UP-TO-DATE +> Task :classes +> Task :resolveMainClassName +> Task :bootJar +> Task :jar +> Task :assemble +> Task :compileTestJava UP-TO-DATE +> Task :processTestResources NO-SOURCE +> Task :testClasses UP-TO-DATE +OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended +2024-10-25T09:16:56.853+09:00 INFO 286 --- [co-co-gong-server] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactor +y for persistence unit 'default' +2024-10-25T09:16:56.855+09:00 INFO 286 --- [co-co-gong-server] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiat +ed... +2024-10-25T09:16:56.857+09:00 INFO 286 --- [co-co-gong-server] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown complet +ed. +> Task :test +> Task :check +> Task :build + +BUILD SUCCESSFUL in 28s +7 actionable tasks: 5 executed, 2 up-to-date + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + + :: Spring Boot :: (v3.3.2) +``` + +## Stop + +```bash +$ kubectl delete ns co-co-gong +namespace "co-co-gong" deleted +``` From 96ec1ac2606c8609a127360f5ad1d8a9f8a77163 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 17:06:55 +0900 Subject: [PATCH 02/24] :sparkles: feat: friend (related: #18) --- build.gradle | 4 + .../friend/controller/FriendController.java | 80 ++++++++++++ ...riendListDto.java => GetFriendOutDto.java} | 10 +- .../server/domain/friend/entity/Friend.java | 50 +++++++ .../domain/friend/enums/FriendState.java | 4 +- .../domain/friend/mapper/FriendMapper.java | 16 +++ .../friend/repository/FriendRepository.java | 25 ++++ .../domain/friend/service/FriendService.java | 122 ++++++++++++++++++ .../com/server/domain/user/dto/UserDto.java | 12 +- 9 files changed, 305 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/server/domain/friend/controller/FriendController.java rename src/main/java/com/server/domain/friend/dto/{FriendListDto.java => GetFriendOutDto.java} (52%) create mode 100644 src/main/java/com/server/domain/friend/entity/Friend.java create mode 100644 src/main/java/com/server/domain/friend/mapper/FriendMapper.java create mode 100644 src/main/java/com/server/domain/friend/repository/FriendRepository.java create mode 100644 src/main/java/com/server/domain/friend/service/FriendService.java diff --git a/build.gradle b/build.gradle index 03cff19..bbcb8c6 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,10 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // MapStruct + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.h2database:h2' diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java new file mode 100644 index 0000000..343e1b2 --- /dev/null +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -0,0 +1,80 @@ +package com.server.domain.friend.controller; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.server.domain.friend.dto.GetFriendOutDto; +import com.server.domain.friend.enums.FriendState; +import com.server.domain.friend.service.FriendService; +import com.server.domain.user.service.UserService; +import com.server.global.jwt.JwtService; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/friends") +public class FriendController { + + private final JwtService jwtService; + private final UserService userService; + private final FriendService friendService; + + @PostMapping("/request") + @Operation(summary = "친구 신청 생성", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 생성") + public ResponseEntity createFriendRequest(@RequestParam String receiptUsername, + HttpServletRequest request) { + String requestUsername = jwtService.extractUserNameFromToken(request).get(); + friendService.createFriendRequest(requestUsername, receiptUsername); + return ResponseEntity.status(HttpStatus.CREATED).body("Created"); + } + + @GetMapping("/request") + @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 보낸 내역") + public ResponseEntity> getFriendRequest(@RequestParam(required = false) FriendState state, + HttpServletRequest request) { + String username = jwtService.extractUserNameFromToken(request).get(); + List getUserOutDtos = friendService.getRequestUser(username, state); + return ResponseEntity.status(HttpStatus.OK).body(getUserOutDtos); + } + + @GetMapping("/receipt") + @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") + public ResponseEntity> getFriendReceipt(@RequestParam(required = false) FriendState state, + HttpServletRequest request) { + String username = jwtService.extractUserNameFromToken(request).get(); + List getUserOutDtos = friendService.getReceiptUser(username, state); + return ResponseEntity.status(HttpStatus.OK).body(getUserOutDtos); + } + + @DeleteMapping("/request") + @Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제") + public ResponseEntity deleteFriendRequest(@RequestParam String receiptUsername, + HttpServletRequest request) { + String requestUsername = jwtService.extractUserNameFromToken(request).get(); + friendService.deleteFriendRequest(requestUsername, receiptUsername); + return ResponseEntity.status(HttpStatus.OK).body("removed"); + } + + @PutMapping("/approve") + @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") + public ResponseEntity approveFriendRequest(@RequestParam String requestUsername, + HttpServletRequest request) { + String receiptUsername = jwtService.extractUserNameFromToken(request).get(); + friendService.approveFriendRequest(requestUsername, receiptUsername); + return ResponseEntity.status(HttpStatus.OK).body("approved"); + } +} diff --git a/src/main/java/com/server/domain/friend/dto/FriendListDto.java b/src/main/java/com/server/domain/friend/dto/GetFriendOutDto.java similarity index 52% rename from src/main/java/com/server/domain/friend/dto/FriendListDto.java rename to src/main/java/com/server/domain/friend/dto/GetFriendOutDto.java index bab1126..e1d90f6 100644 --- a/src/main/java/com/server/domain/friend/dto/FriendListDto.java +++ b/src/main/java/com/server/domain/friend/dto/GetFriendOutDto.java @@ -1,15 +1,13 @@ package com.server.domain.friend.dto; -import java.util.UUID; - import com.server.domain.friend.enums.FriendState; +import com.server.domain.user.dto.UserDto; import lombok.Getter; +import lombok.Setter; @Getter -public class FriendListDto { - private UUID id; - private UUID requestUserId; - private UUID receiptUserId; +@Setter +public class GetFriendOutDto extends UserDto { private FriendState state; } diff --git a/src/main/java/com/server/domain/friend/entity/Friend.java b/src/main/java/com/server/domain/friend/entity/Friend.java new file mode 100644 index 0000000..4fd4a15 --- /dev/null +++ b/src/main/java/com/server/domain/friend/entity/Friend.java @@ -0,0 +1,50 @@ +package com.server.domain.friend.entity; + +import com.server.domain.friend.enums.FriendState; +import com.server.domain.user.entity.User; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "friends", uniqueConstraints = { + @UniqueConstraint(columnNames = { "request_user_id", "receipt_user_id" }) }) +public class Friend { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "friend_id") + private Long id; + + @ManyToOne + @JoinColumn(name = "request_user_id", nullable = false) + private User requestUser; + + @ManyToOne + @JoinColumn(name = "receipt_user_id", nullable = false) + private User receiptUser; + + @Column(name = "state") + @Enumerated(EnumType.STRING) + @Setter + private FriendState state; + +} diff --git a/src/main/java/com/server/domain/friend/enums/FriendState.java b/src/main/java/com/server/domain/friend/enums/FriendState.java index 1bc7f8f..fe0ba2e 100644 --- a/src/main/java/com/server/domain/friend/enums/FriendState.java +++ b/src/main/java/com/server/domain/friend/enums/FriendState.java @@ -2,6 +2,6 @@ public enum FriendState { SENDING, - REMOVE, - APPROVE + REMOVED, + APPROVED } diff --git a/src/main/java/com/server/domain/friend/mapper/FriendMapper.java b/src/main/java/com/server/domain/friend/mapper/FriendMapper.java new file mode 100644 index 0000000..4f35791 --- /dev/null +++ b/src/main/java/com/server/domain/friend/mapper/FriendMapper.java @@ -0,0 +1,16 @@ +package com.server.domain.friend.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import com.server.domain.friend.dto.GetFriendOutDto; +import com.server.domain.friend.enums.FriendState; +import com.server.domain.user.entity.User; + +@Mapper(componentModel = "spring") +public interface FriendMapper { + + FriendMapper INSTANCE = Mappers.getMapper(FriendMapper.class); + + GetFriendOutDto toGetFriendOutDto(User user, FriendState state); +} diff --git a/src/main/java/com/server/domain/friend/repository/FriendRepository.java b/src/main/java/com/server/domain/friend/repository/FriendRepository.java new file mode 100644 index 0000000..c0a6115 --- /dev/null +++ b/src/main/java/com/server/domain/friend/repository/FriendRepository.java @@ -0,0 +1,25 @@ +package com.server.domain.friend.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.server.domain.friend.entity.Friend; +import com.server.domain.friend.enums.FriendState; +import com.server.domain.user.entity.User; + +@Repository +public interface FriendRepository extends JpaRepository { + + Optional> findByRequestUser(User requestUser); + + Optional> findByReceiptUser(User receiptUser); + + Optional findByRequestUserAndReceiptUser(User requestUser, User receiptUser); + + Optional> findByRequestUserAndState(User requestUser, FriendState state); + + Optional> findByReceiptUserAndState(User receiptUser, FriendState state); +} diff --git a/src/main/java/com/server/domain/friend/service/FriendService.java b/src/main/java/com/server/domain/friend/service/FriendService.java new file mode 100644 index 0000000..1875ed8 --- /dev/null +++ b/src/main/java/com/server/domain/friend/service/FriendService.java @@ -0,0 +1,122 @@ +package com.server.domain.friend.service; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.server.domain.friend.dto.GetFriendOutDto; +import com.server.domain.friend.entity.Friend; +import com.server.domain.friend.enums.FriendState; +import com.server.domain.friend.mapper.FriendMapper; +import com.server.domain.friend.repository.FriendRepository; +import com.server.domain.user.entity.User; +import com.server.domain.user.repository.UserRepository; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class FriendService { + + private final UserRepository userRepository; + private final FriendRepository friendRepository; + private final FriendMapper friendMapper; + + public List getRequestUser(String username, FriendState state) { + List friends; + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + if (state == null) { + friends = friendRepository.findByRequestUser(user) + .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + } else { + friends = friendRepository.findByRequestUserAndState(user, state) + .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + } + List getFriendOutDtos = friends.stream() + .map(friend -> friendMapper.toGetFriendOutDto(friend.getReceiptUser(), friend.getState())) + .collect(Collectors.toList()); + return getFriendOutDtos; + } + + public List getReceiptUser(String username, FriendState state) { + List friends; + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + if (state == null) { + friends = friendRepository.findByReceiptUser(user) + .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + } else { + friends = friendRepository.findByReceiptUserAndState(user, state) + .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + } + List getFriendOutDtos = friends.stream() + .map(friend -> friendMapper.toGetFriendOutDto(friend.getRequestUser(), friend.getState())) + .collect(Collectors.toList()); + return getFriendOutDtos; + } + + @Transactional + public Friend createFriendRequest(String requestUsername, String receiptUsername) { + User requestUser = userRepository.findByUsername(requestUsername) + .orElseThrow( + () -> new EntityNotFoundException("Request user not found with username: " + requestUsername)); + User receiptUser = userRepository.findByUsername(receiptUsername) + .orElseThrow( + () -> new EntityNotFoundException("Receipt user not found with username: " + receiptUsername)); + Friend friend = Friend.builder() + .requestUser(requestUser) + .receiptUser(receiptUser) + .state(FriendState.SENDING) + .build(); + return friendRepository.save(friend); + } + + @Transactional + public void deleteFriendRequest(String requestUsername, String receiptUsername) { + // requestUser와 receiptUser를 UserRepository에서 조회 + User requestUser = userRepository.findByUsername(requestUsername) + .orElseThrow( + () -> new EntityNotFoundException("Request user not found with username: " + + requestUsername)); + User receiptUser = userRepository.findByUsername(receiptUsername) + .orElseThrow( + () -> new EntityNotFoundException("Receipt user not found with username: " + + receiptUsername)); + Friend friend = friendRepository.findByRequestUserAndReceiptUser(requestUser, + receiptUser) + .orElseThrow( + () -> new EntityNotFoundException( + "Friend list not found with username: " + requestUsername + ", " + + receiptUsername)); + friend.setState(FriendState.REMOVED); + } + + @Transactional + public void approveFriendRequest(String requestUsername, String receiptUsername) { + User requestUser = userRepository.findByUsername(requestUsername) + .orElseThrow( + () -> new EntityNotFoundException("Request user not found with username: " + + requestUsername)); + User receiptUser = userRepository.findByUsername(receiptUsername) + .orElseThrow( + () -> new EntityNotFoundException("Receipt user not found with username: " + + receiptUsername)); + Friend friendRequested = friendRepository.findByRequestUserAndReceiptUser(requestUser, + receiptUser) + .orElseThrow( + () -> new EntityNotFoundException( + "Friend list not found with username: " + requestUsername + ", " + + receiptUsername)); + friendRequested.setState(FriendState.APPROVED); + Friend friendApproved = Friend.builder() + .requestUser(receiptUser) + .receiptUser(requestUser) + .state(FriendState.APPROVED) + .build(); + friendRepository.save(friendApproved); + } +} diff --git a/src/main/java/com/server/domain/user/dto/UserDto.java b/src/main/java/com/server/domain/user/dto/UserDto.java index 340cae0..79a44fd 100644 --- a/src/main/java/com/server/domain/user/dto/UserDto.java +++ b/src/main/java/com/server/domain/user/dto/UserDto.java @@ -1,21 +1,13 @@ package com.server.domain.user.dto; -import java.time.LocalDateTime; -import java.util.UUID; - -import com.server.domain.user.enums.OAuth; - import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Getter +@Setter @NoArgsConstructor public class UserDto { - private Long id; private String username; private String email; - private OAuth oauth; - private String githubToken; - private LocalDateTime createdAt; - private String refreshToken; } From d51c14f1815d463e240c418d34fba0c66f78fed1 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 17:19:48 +0900 Subject: [PATCH 03/24] :recycle: refactor: from 'userName' to 'username' --- .../friend/controller/FriendController.java | 10 +- .../controller/OAuthLoginController.java | 6 +- .../user/controller/UserController.java | 173 +++++++++--------- .../com/server/domain/user/entity/User.java | 5 +- .../user/repository/UserRepository.java | 4 +- .../domain/user/service/UserService.java | 22 +-- .../com/server/global/jwt/JwtService.java | 56 +++--- 7 files changed, 138 insertions(+), 138 deletions(-) diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index 343e1b2..0d07846 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -37,7 +37,7 @@ public class FriendController { @Operation(summary = "친구 신청 생성", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 생성") public ResponseEntity createFriendRequest(@RequestParam String receiptUsername, HttpServletRequest request) { - String requestUsername = jwtService.extractUserNameFromToken(request).get(); + String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.createFriendRequest(requestUsername, receiptUsername); return ResponseEntity.status(HttpStatus.CREATED).body("Created"); } @@ -46,7 +46,7 @@ public ResponseEntity createFriendRequest(@RequestParam String receiptUs @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 보낸 내역") public ResponseEntity> getFriendRequest(@RequestParam(required = false) FriendState state, HttpServletRequest request) { - String username = jwtService.extractUserNameFromToken(request).get(); + String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getRequestUser(username, state); return ResponseEntity.status(HttpStatus.OK).body(getUserOutDtos); } @@ -55,7 +55,7 @@ public ResponseEntity> getFriendRequest(@RequestParam(requ @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") public ResponseEntity> getFriendReceipt(@RequestParam(required = false) FriendState state, HttpServletRequest request) { - String username = jwtService.extractUserNameFromToken(request).get(); + String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getReceiptUser(username, state); return ResponseEntity.status(HttpStatus.OK).body(getUserOutDtos); } @@ -64,7 +64,7 @@ public ResponseEntity> getFriendReceipt(@RequestParam(requ @Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제") public ResponseEntity deleteFriendRequest(@RequestParam String receiptUsername, HttpServletRequest request) { - String requestUsername = jwtService.extractUserNameFromToken(request).get(); + String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.deleteFriendRequest(requestUsername, receiptUsername); return ResponseEntity.status(HttpStatus.OK).body("removed"); } @@ -73,7 +73,7 @@ public ResponseEntity deleteFriendRequest(@RequestParam String receiptUs @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") public ResponseEntity approveFriendRequest(@RequestParam String requestUsername, HttpServletRequest request) { - String receiptUsername = jwtService.extractUserNameFromToken(request).get(); + String receiptUsername = jwtService.extractUsernameFromToken(request).get(); friendService.approveFriendRequest(requestUsername, receiptUsername); return ResponseEntity.status(HttpStatus.OK).body("approved"); } diff --git a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java index c65b96c..2c2ad35 100644 --- a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java +++ b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java @@ -106,11 +106,11 @@ public ResponseEntity refreshToken(@RequestBody TokenDto tokenDto) { String refreshToken = tokenDto.getRefreshToken(); if (jwtService.validateToken(refreshToken)) { - String userName = jwtService.extractUserName(refreshToken).get(); - Optional user = userService.findByUserName(userName); + String username = jwtService.extractUsername(refreshToken).get(); + Optional user = userService.findByUsername(username); if (user.isPresent()) { if (refreshToken.equals(user.get().getRefreshToken())) { - String newAccessToken = jwtService.createAccessToken(userName); + String newAccessToken = jwtService.createAccessToken(username); TokenDto newTokenDto = new TokenDto(newAccessToken, refreshToken); return ResponseEntity.ok(ApiResponseDto.success(newTokenDto)); } diff --git a/src/main/java/com/server/domain/user/controller/UserController.java b/src/main/java/com/server/domain/user/controller/UserController.java index d46cfab..4784299 100644 --- a/src/main/java/com/server/domain/user/controller/UserController.java +++ b/src/main/java/com/server/domain/user/controller/UserController.java @@ -1,95 +1,92 @@ - package com.server.domain.user.controller; - - import java.util.Optional; - - import com.server.global.jwt.JwtService; - import jakarta.servlet.http.HttpServletRequest; - import lombok.RequiredArgsConstructor; - import lombok.extern.slf4j.Slf4j; - import org.springframework.http.HttpStatus; - import org.springframework.http.ResponseEntity; - 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.PutMapping; - import org.springframework.web.bind.annotation.RequestMapping; - import org.springframework.web.bind.annotation.RestController; - - import com.server.domain.user.entity.User; - import com.server.domain.user.service.UserService; - - @RestController - @RequiredArgsConstructor - @Slf4j - @RequestMapping("/api/users") - public class UserController { - - private final JwtService jwtService; - private final UserService userService; - - //내 정보 - @GetMapping("/me") - public ResponseEntity getUser(HttpServletRequest request) { - // 디버깅을 위한 로그 추가 - log.info("Received request to /me endpoint"); - - // request(token)에서 username 추출 - Optional username = jwtService.extractUserNameFromToken(request); - if (username.isEmpty()) { - log.warn("Failed to extract username from token"); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); - } - - log.info("Extracted username: {}", username.get()); - - // username으로 찾은 user 반환 - Optional user = userService.findByUserName(username.get()); - - return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); - } - - - @GetMapping("/{username}") - public ResponseEntity getUserByUsername(@PathVariable String username) - { - Optional user = userService.findByUserName(username); - return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); - } +package com.server.domain.user.controller; + +import java.util.Optional; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.server.domain.user.entity.User; +import com.server.domain.user.service.UserService; +import com.server.global.jwt.JwtService; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/api/users") +public class UserController { + + private final JwtService jwtService; + private final UserService userService; + + // 내 정보 + @GetMapping("/me") + public ResponseEntity getUser(HttpServletRequest request) { + // 디버깅을 위한 로그 추가 + log.info("Received request to /me endpoint"); + + // request(token)에서 username 추출 + Optional username = jwtService.extractUsernameFromToken(request); + if (username.isEmpty()) { + log.warn("Failed to extract username from token"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); + } + + log.info("Extracted username: {}", username.get()); + + // username으로 찾은 user 반환 + Optional user = userService.findByUsername(username.get()); + return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + @GetMapping("/{username}") + public ResponseEntity getUserByUsername(@PathVariable String username) { + Optional user = userService.findByUsername(username); + return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } - //정보 수정 - //현재 바꿀 수 있는 거 email. 추후 닉네임 추가..? - @PutMapping - public ResponseEntity updateUser(HttpServletRequest request, String email) { - Optional username = jwtService.extractUserNameFromToken(request); - if (username.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); - } + // 정보 수정 + // 현재 바꿀 수 있는 거 email. 추후 닉네임 추가..? + @PutMapping + public ResponseEntity updateUser(HttpServletRequest request, String email) { + Optional username = jwtService.extractUsernameFromToken(request); + if (username.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); + } - // username으로 찾은 user 반환 - Optional user = userService.findByUserName(username.get()); + // username으로 찾은 user 반환 + Optional user = userService.findByUsername(username.get()); - return user.map(value ->{ + return user.map(value -> { User changedUser = userService.updateEmail(value, email); return ResponseEntity.ok(changedUser.getEmail()); - }).orElseGet(()-> ResponseEntity.notFound().build()); - - } - - //사용자 탈퇴 - @DeleteMapping("/me") - public ResponseEntity deleteUser(HttpServletRequest request){ - Optional username = jwtService.extractUserNameFromToken(request); - if (username.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); - } - - // username으로 찾은 user 반환 - Optional user = userService.findByUserName(username.get()); - return user.map(value ->{ - userService.deleteUser(value); - return ResponseEntity.ok("Success delete user "+value.getUsername()); - }).orElseGet(()->ResponseEntity.notFound().build()); - } - } + }).orElseGet(() -> ResponseEntity.notFound().build()); + + } + + // 사용자 탈퇴 + @DeleteMapping("/me") + public ResponseEntity deleteUser(HttpServletRequest request) { + Optional username = jwtService.extractUsernameFromToken(request); + if (username.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); + } + + // username으로 찾은 user 반환 + Optional user = userService.findByUsername(username.get()); + return user.map(value -> { + userService.deleteUser(value); + return ResponseEntity.ok("Success delete user " + value.getUsername()); + }).orElseGet(() -> ResponseEntity.notFound().build()); + } +} diff --git a/src/main/java/com/server/domain/user/entity/User.java b/src/main/java/com/server/domain/user/entity/User.java index afbd0eb..5c27937 100644 --- a/src/main/java/com/server/domain/user/entity/User.java +++ b/src/main/java/com/server/domain/user/entity/User.java @@ -2,9 +2,7 @@ import java.time.LocalDateTime; -import lombok.Builder; import org.hibernate.annotations.CreationTimestamp; -import org.springframework.data.annotation.CreatedDate; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -13,6 +11,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -63,7 +62,7 @@ public void updateRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } - public void updateEmail(String email){ + public void updateEmail(String email) { this.email = email; } } diff --git a/src/main/java/com/server/domain/user/repository/UserRepository.java b/src/main/java/com/server/domain/user/repository/UserRepository.java index 929cc46..c6861f9 100644 --- a/src/main/java/com/server/domain/user/repository/UserRepository.java +++ b/src/main/java/com/server/domain/user/repository/UserRepository.java @@ -1,12 +1,12 @@ package com.server.domain.user.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.server.domain.user.entity.User; -import java.util.Optional; - @Repository public interface UserRepository extends JpaRepository { diff --git a/src/main/java/com/server/domain/user/service/UserService.java b/src/main/java/com/server/domain/user/service/UserService.java index 07e853c..83dd08f 100644 --- a/src/main/java/com/server/domain/user/service/UserService.java +++ b/src/main/java/com/server/domain/user/service/UserService.java @@ -1,15 +1,16 @@ package com.server.domain.user.service; +import java.util.Optional; -import com.server.domain.oauth.dto.GithubDto; -import com.server.domain.user.entity.User; -import com.server.domain.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; +import com.server.domain.oauth.dto.GithubDto; +import com.server.domain.user.entity.User; +import com.server.domain.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor @@ -32,20 +33,19 @@ private User registerNewUser(GithubDto githubDto, String githubToken) { .githubToken(githubToken) .build(); - return userRepository.save(newUser); } @Transactional - public void saveRefreshToken(String userName, String refreshToken) { - User user = userRepository.findByUsername(userName) + public void saveRefreshToken(String username, String refreshToken) { + User user = userRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("User not found")); user.updateRefreshToken(refreshToken); userRepository.save(user); } - public Optional findByUserName(String userName) { - return userRepository.findByUsername(userName); + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); } public User updateEmail(User user, String email) { @@ -57,4 +57,4 @@ public User updateEmail(User user, String email) { public void deleteUser(User user) { userRepository.delete(user); } -} \ No newline at end of file +} diff --git a/src/main/java/com/server/global/jwt/JwtService.java b/src/main/java/com/server/global/jwt/JwtService.java index e8178de..87bf9a4 100644 --- a/src/main/java/com/server/global/jwt/JwtService.java +++ b/src/main/java/com/server/global/jwt/JwtService.java @@ -1,17 +1,21 @@ package com.server.global.jwt; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; -import jakarta.servlet.http.HttpServletRequest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Optional; +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; + @Service @Slf4j public class JwtService { @@ -24,7 +28,7 @@ public class JwtService { @Value("${jwt.refresh-token-expiration}") private long refreshTokenExpiration; - private static final String USERNAME_CLAIM = "userName"; + private static final String USERNAME_CLAIM = "username"; private static final String BEARER = "Bearer "; private SecretKey getSigningKey() { @@ -32,27 +36,27 @@ private SecretKey getSigningKey() { return Keys.hmacShaKeyFor(keyBytes); } - public String createAccessToken(String userName) { - return createToken(userName, accessTokenExpiration); + public String createAccessToken(String username) { + return createToken(username, accessTokenExpiration); } - public String createRefreshToken(String userName) { - return createToken(userName, refreshTokenExpiration); + public String createRefreshToken(String username) { + return createToken(username, refreshTokenExpiration); } - private String createToken(String userName, long expiration) { + private String createToken(String username, long expiration) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration); String token = Jwts.builder() - .setSubject(userName) - .claim(USERNAME_CLAIM, userName) // 명시적으로 USERNAME_CLAIM 추가 + .setSubject(username) + .claim(USERNAME_CLAIM, username) // 명시적으로 USERNAME_CLAIM 추가 .setIssuedAt(now) .setExpiration(expiryDate) .signWith(getSigningKey(), SignatureAlgorithm.HS512) .compact(); - log.info("Created token for user: {}, token: {}", userName, token); + log.info("Created token for user: {}, token: {}", username, token); return token; } @@ -67,7 +71,7 @@ public boolean validateToken(String token) { } } - public Optional extractUserName(String token) { + public Optional extractUsername(String token) { try { Claims claims = Jwts.parserBuilder() .setSigningKey(getSigningKey()) @@ -75,12 +79,12 @@ public Optional extractUserName(String token) { .parseClaimsJws(token) .getBody(); - String userName = claims.get(USERNAME_CLAIM, String.class); - if (userName == null) { - userName = claims.getSubject(); // USERNAME_CLAIM이 없으면 subject를 사용 + String username = claims.get(USERNAME_CLAIM, String.class); + if (username == null) { + username = claims.getSubject(); // USERNAME_CLAIM이 없으면 subject를 사용 } - log.info("Extracted username from token: {}", userName); - return Optional.ofNullable(userName); + log.info("Extracted username from token: {}", username); + return Optional.ofNullable(username); } catch (Exception e) { log.error("Failed to extract username from token: {}", e.getMessage()); return Optional.empty(); @@ -100,7 +104,7 @@ public Optional extractAccessToken(HttpServletRequest request) { }); } - public Optional extractUserNameFromToken(HttpServletRequest request) { + public Optional extractUsernameFromToken(HttpServletRequest request) { Optional accessToken = extractAccessToken(request); if (accessToken.isEmpty()) { log.warn("Access token is empty"); @@ -112,6 +116,6 @@ public Optional extractUserNameFromToken(HttpServletRequest request) { return Optional.empty(); } - return extractUserName(accessToken.get()); + return extractUsername(accessToken.get()); } -} \ No newline at end of file +} From da1105dd9fc5c43b2b5d53d182c6ba345d14f551 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 17:50:05 +0900 Subject: [PATCH 04/24] :sparkles: feat: updated at --- .../java/com/server/CoCoGongApplication.java | 2 ++ .../com/server/domain/friend/entity/Friend.java | 16 ++++++++++++++++ .../java/com/server/domain/user/entity/User.java | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/src/main/java/com/server/CoCoGongApplication.java b/src/main/java/com/server/CoCoGongApplication.java index 7ab5027..fc3307c 100644 --- a/src/main/java/com/server/CoCoGongApplication.java +++ b/src/main/java/com/server/CoCoGongApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class CoCoGongApplication { public static void main(String[] args) { diff --git a/src/main/java/com/server/domain/friend/entity/Friend.java b/src/main/java/com/server/domain/friend/entity/Friend.java index 4fd4a15..2a6fd7b 100644 --- a/src/main/java/com/server/domain/friend/entity/Friend.java +++ b/src/main/java/com/server/domain/friend/entity/Friend.java @@ -1,10 +1,17 @@ package com.server.domain.friend.entity; +import java.time.LocalDateTime; + +import org.hibernate.annotations.CreationTimestamp; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + import com.server.domain.friend.enums.FriendState; import com.server.domain.user.entity.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; @@ -21,6 +28,7 @@ import lombok.Setter; @Entity +@EntityListeners(AuditingEntityListener.class) @Getter @NoArgsConstructor @AllArgsConstructor @@ -47,4 +55,12 @@ public class Friend { @Setter private FriendState state; + @Column(name = "created_at", nullable = false) + @CreationTimestamp + private LocalDateTime createdAt; + + @Column(name = "updated_at", nullable = false) + @LastModifiedDate + private LocalDateTime updatedAt; + } diff --git a/src/main/java/com/server/domain/user/entity/User.java b/src/main/java/com/server/domain/user/entity/User.java index 5c27937..5148ad7 100644 --- a/src/main/java/com/server/domain/user/entity/User.java +++ b/src/main/java/com/server/domain/user/entity/User.java @@ -3,9 +3,12 @@ import java.time.LocalDateTime; import org.hibernate.annotations.CreationTimestamp; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -16,6 +19,7 @@ import lombok.NoArgsConstructor; @Entity +@EntityListeners(AuditingEntityListener.class) @Getter @NoArgsConstructor @AllArgsConstructor @@ -46,6 +50,10 @@ public class User { @CreationTimestamp private LocalDateTime createdAt; + @Column(name = "updated_at", nullable = false) + @LastModifiedDate + private LocalDateTime updatedAt; + @Column(name = "refresh_token", length = 512) private String refreshToken; From 3fd7c1e248dd55f60564477249e69e54fe3e7558 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 21:02:32 +0900 Subject: [PATCH 05/24] :memo: modify: api response dto --- .../friend/controller/FriendController.java | 23 +++++++++++-------- .../com/server/global/dto/ApiResponseDto.java | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index 0d07846..2e60494 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -16,6 +16,7 @@ import com.server.domain.friend.enums.FriendState; import com.server.domain.friend.service.FriendService; import com.server.domain.user.service.UserService; +import com.server.global.dto.ApiResponseDto; import com.server.global.jwt.JwtService; import io.swagger.v3.oas.annotations.Operation; @@ -35,46 +36,48 @@ public class FriendController { @PostMapping("/request") @Operation(summary = "친구 신청 생성", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 생성") - public ResponseEntity createFriendRequest(@RequestParam String receiptUsername, + public ResponseEntity> createFriendRequest(@RequestParam String receiptUsername, HttpServletRequest request) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.createFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.CREATED).body("Created"); + return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponseDto.success("Created")); } @GetMapping("/request") @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 보낸 내역") - public ResponseEntity> getFriendRequest(@RequestParam(required = false) FriendState state, + public ResponseEntity>> getFriendRequest( + @RequestParam(required = false) FriendState state, HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getRequestUser(username, state); - return ResponseEntity.status(HttpStatus.OK).body(getUserOutDtos); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success(getUserOutDtos)); } @GetMapping("/receipt") @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") - public ResponseEntity> getFriendReceipt(@RequestParam(required = false) FriendState state, + public ResponseEntity>> getFriendReceipt( + @RequestParam(required = false) FriendState state, HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getReceiptUser(username, state); - return ResponseEntity.status(HttpStatus.OK).body(getUserOutDtos); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success(getUserOutDtos)); } @DeleteMapping("/request") @Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제") - public ResponseEntity deleteFriendRequest(@RequestParam String receiptUsername, + public ResponseEntity> deleteFriendRequest(@RequestParam String receiptUsername, HttpServletRequest request) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.deleteFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.OK).body("removed"); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success("removed")); } @PutMapping("/approve") @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") - public ResponseEntity approveFriendRequest(@RequestParam String requestUsername, + public ResponseEntity> approveFriendRequest(@RequestParam String requestUsername, HttpServletRequest request) { String receiptUsername = jwtService.extractUsernameFromToken(request).get(); friendService.approveFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.OK).body("approved"); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success("approved")); } } diff --git a/src/main/java/com/server/global/dto/ApiResponseDto.java b/src/main/java/com/server/global/dto/ApiResponseDto.java index e78bfd1..9dcc6f0 100644 --- a/src/main/java/com/server/global/dto/ApiResponseDto.java +++ b/src/main/java/com/server/global/dto/ApiResponseDto.java @@ -19,4 +19,4 @@ public static ApiResponseDto success(T data) { public static ApiResponseDto failure(String message) { return new ApiResponseDto<>(false, null, message); } -} \ No newline at end of file +} From da3d70cd6288982bf899206e682c918449877dfe Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 23:01:54 +0900 Subject: [PATCH 06/24] :hammer: fix: gradle cache --- k8s/backend.yaml | 6 ++++++ scripts/local.sh | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/k8s/backend.yaml b/k8s/backend.yaml index 87180ca..2a88640 100644 --- a/k8s/backend.yaml +++ b/k8s/backend.yaml @@ -67,11 +67,17 @@ spec: volumeMounts: - mountPath: /home/zerohertz/workspace name: backend-storage + - mountPath: /home/zerohertz/.gradle + name: gradle-cache volumes: - name: backend-storage hostPath: path: /home/zerohertz/Zerohertz/co-co-gong-server type: DirectoryOrCreate + - name: gradle-cache + hostPath: + path: /home/zerohertz/.gradle + type: DirectoryOrCreate --- apiVersion: v1 kind: Service diff --git a/scripts/local.sh b/scripts/local.sh index 8bff8f1..bcc9e7b 100755 --- a/scripts/local.sh +++ b/scripts/local.sh @@ -1,4 +1,6 @@ #!/bin/bash -./gradlew build +rm -r build + +./gradlew clean build java -jar build/libs/*SNAPSHOT.jar From 218fe2eb0056a9231d069f3575c4310bbbe210c7 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 23:10:37 +0900 Subject: [PATCH 07/24] :warning: add: exception handler --- .../friend/controller/FriendController.java | 2 - .../domain/friend/service/FriendService.java | 55 +++++++------------ .../server/global/error/code/ErrorCode.java | 9 +++ .../global/error/code/FriendErrorCode.java | 17 ++++++ .../global/error/code/UserErrorCode.java | 15 +++++ .../global/error/exception/BaseException.java | 16 ++++++ .../error/exception/BusinessException.java | 12 ++++ .../exception/GlobalExceptionHandler.java | 20 +++++++ 8 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/server/global/error/code/ErrorCode.java create mode 100644 src/main/java/com/server/global/error/code/FriendErrorCode.java create mode 100644 src/main/java/com/server/global/error/code/UserErrorCode.java create mode 100644 src/main/java/com/server/global/error/exception/BaseException.java create mode 100644 src/main/java/com/server/global/error/exception/BusinessException.java create mode 100644 src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index 2e60494..834b64d 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -15,7 +15,6 @@ import com.server.domain.friend.dto.GetFriendOutDto; import com.server.domain.friend.enums.FriendState; import com.server.domain.friend.service.FriendService; -import com.server.domain.user.service.UserService; import com.server.global.dto.ApiResponseDto; import com.server.global.jwt.JwtService; @@ -31,7 +30,6 @@ public class FriendController { private final JwtService jwtService; - private final UserService userService; private final FriendService friendService; @PostMapping("/request") diff --git a/src/main/java/com/server/domain/friend/service/FriendService.java b/src/main/java/com/server/domain/friend/service/FriendService.java index 1875ed8..1a8d2ad 100644 --- a/src/main/java/com/server/domain/friend/service/FriendService.java +++ b/src/main/java/com/server/domain/friend/service/FriendService.java @@ -13,8 +13,10 @@ import com.server.domain.friend.repository.FriendRepository; import com.server.domain.user.entity.User; import com.server.domain.user.repository.UserRepository; +import com.server.global.error.code.FriendErrorCode; +import com.server.global.error.code.UserErrorCode; +import com.server.global.error.exception.BusinessException; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @Service @@ -28,13 +30,13 @@ public class FriendService { public List getRequestUser(String username, FriendState state) { List friends; User user = userRepository.findByUsername(username) - .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); if (state == null) { friends = friendRepository.findByRequestUser(user) - .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); } else { friends = friendRepository.findByRequestUserAndState(user, state) - .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); } List getFriendOutDtos = friends.stream() .map(friend -> friendMapper.toGetFriendOutDto(friend.getReceiptUser(), friend.getState())) @@ -45,13 +47,13 @@ public List getRequestUser(String username, FriendState state) public List getReceiptUser(String username, FriendState state) { List friends; User user = userRepository.findByUsername(username) - .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); if (state == null) { friends = friendRepository.findByReceiptUser(user) - .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); } else { friends = friendRepository.findByReceiptUserAndState(user, state) - .orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); } List getFriendOutDtos = friends.stream() .map(friend -> friendMapper.toGetFriendOutDto(friend.getRequestUser(), friend.getState())) @@ -62,11 +64,9 @@ public List getReceiptUser(String username, FriendState state) @Transactional public Friend createFriendRequest(String requestUsername, String receiptUsername) { User requestUser = userRepository.findByUsername(requestUsername) - .orElseThrow( - () -> new EntityNotFoundException("Request user not found with username: " + requestUsername)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); User receiptUser = userRepository.findByUsername(receiptUsername) - .orElseThrow( - () -> new EntityNotFoundException("Receipt user not found with username: " + receiptUsername)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); Friend friend = Friend.builder() .requestUser(requestUser) .receiptUser(receiptUser) @@ -77,40 +77,23 @@ public Friend createFriendRequest(String requestUsername, String receiptUsername @Transactional public void deleteFriendRequest(String requestUsername, String receiptUsername) { - // requestUser와 receiptUser를 UserRepository에서 조회 User requestUser = userRepository.findByUsername(requestUsername) - .orElseThrow( - () -> new EntityNotFoundException("Request user not found with username: " + - requestUsername)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); User receiptUser = userRepository.findByUsername(receiptUsername) - .orElseThrow( - () -> new EntityNotFoundException("Receipt user not found with username: " + - receiptUsername)); - Friend friend = friendRepository.findByRequestUserAndReceiptUser(requestUser, - receiptUser) - .orElseThrow( - () -> new EntityNotFoundException( - "Friend list not found with username: " + requestUsername + ", " + - receiptUsername)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + Friend friend = friendRepository.findByRequestUserAndReceiptUser(requestUser, receiptUser) + .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); friend.setState(FriendState.REMOVED); } @Transactional public void approveFriendRequest(String requestUsername, String receiptUsername) { User requestUser = userRepository.findByUsername(requestUsername) - .orElseThrow( - () -> new EntityNotFoundException("Request user not found with username: " + - requestUsername)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); User receiptUser = userRepository.findByUsername(receiptUsername) - .orElseThrow( - () -> new EntityNotFoundException("Receipt user not found with username: " + - receiptUsername)); - Friend friendRequested = friendRepository.findByRequestUserAndReceiptUser(requestUser, - receiptUser) - .orElseThrow( - () -> new EntityNotFoundException( - "Friend list not found with username: " + requestUsername + ", " + - receiptUsername)); + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + Friend friendRequested = friendRepository.findByRequestUserAndReceiptUser(requestUser, receiptUser) + .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); friendRequested.setState(FriendState.APPROVED); Friend friendApproved = Friend.builder() .requestUser(receiptUser) diff --git a/src/main/java/com/server/global/error/code/ErrorCode.java b/src/main/java/com/server/global/error/code/ErrorCode.java new file mode 100644 index 0000000..7c7cdfc --- /dev/null +++ b/src/main/java/com/server/global/error/code/ErrorCode.java @@ -0,0 +1,9 @@ +package com.server.global.error.code; + +public interface ErrorCode { + + int getStatus(); + + String getMessage(); + +} diff --git a/src/main/java/com/server/global/error/code/FriendErrorCode.java b/src/main/java/com/server/global/error/code/FriendErrorCode.java new file mode 100644 index 0000000..6b45425 --- /dev/null +++ b/src/main/java/com/server/global/error/code/FriendErrorCode.java @@ -0,0 +1,17 @@ +package com.server.global.error.code; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum FriendErrorCode implements ErrorCode { + ALREADY_FRIENDS(HttpStatus.CONFLICT.value(), "이미 친구 상태입니다."), + ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "친구 신청을 이미 완료했습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND.value(), "친구 신청이 존재하지 않습니다."); + + private final int status; + private final String message; +} diff --git a/src/main/java/com/server/global/error/code/UserErrorCode.java b/src/main/java/com/server/global/error/code/UserErrorCode.java new file mode 100644 index 0000000..2d4502f --- /dev/null +++ b/src/main/java/com/server/global/error/code/UserErrorCode.java @@ -0,0 +1,15 @@ +package com.server.global.error.code; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum UserErrorCode implements ErrorCode { + NOT_FOUND(HttpStatus.NOT_FOUND.value(), "사용자가 존재하지 않습니다."); + + private final int status; + private final String message; +} diff --git a/src/main/java/com/server/global/error/exception/BaseException.java b/src/main/java/com/server/global/error/exception/BaseException.java new file mode 100644 index 0000000..cc73a33 --- /dev/null +++ b/src/main/java/com/server/global/error/exception/BaseException.java @@ -0,0 +1,16 @@ +package com.server.global.error.exception; + +import com.server.global.error.code.ErrorCode; + +import lombok.Getter; + +@Getter +public class BaseException extends RuntimeException { + private final int status; + private final String message; + + public BaseException(ErrorCode errorCode) { + this.status = errorCode.getStatus(); + this.message = errorCode.getMessage(); + } +} diff --git a/src/main/java/com/server/global/error/exception/BusinessException.java b/src/main/java/com/server/global/error/exception/BusinessException.java new file mode 100644 index 0000000..8f2a28c --- /dev/null +++ b/src/main/java/com/server/global/error/exception/BusinessException.java @@ -0,0 +1,12 @@ +package com.server.global.error.exception; + +import com.server.global.error.code.ErrorCode; + +import lombok.Getter; + +@Getter +public class BusinessException extends BaseException { + public BusinessException(final ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java b/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..4bbbc32 --- /dev/null +++ b/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java @@ -0,0 +1,20 @@ +package com.server.global.error.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.server.global.dto.ApiResponseDto; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public ResponseEntity> handleFriendException(BusinessException e) { + return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.failure(e.getMessage())); + } + +} From e8de2b1ee2a6c2e710eb8e73877aaca3932f3dbc Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Mon, 28 Oct 2024 23:39:46 +0900 Subject: [PATCH 08/24] :bento: update: error handler (timestamp, status) --- .../friend/controller/FriendController.java | 21 +++++++---- .../controller/OAuthLoginController.java | 9 ++--- .../com/server/global/dto/ApiResponseDto.java | 35 +++++++++++++------ .../exception/GlobalExceptionHandler.java | 2 +- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index 834b64d..1703a93 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -38,7 +38,10 @@ public ResponseEntity> createFriendRequest(@RequestParam HttpServletRequest request) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.createFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponseDto.success("Created")); + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponseDto.success(HttpStatus.CREATED.value(), + String.format("A friend request from User '%s' to User '%s' has been created.", + requestUsername, receiptUsername))); } @GetMapping("/request") @@ -48,7 +51,7 @@ public ResponseEntity>> getFriendRequest( HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getRequestUser(username, state); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success(getUserOutDtos)); + return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); } @GetMapping("/receipt") @@ -58,7 +61,7 @@ public ResponseEntity>> getFriendReceipt( HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getReceiptUser(username, state); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success(getUserOutDtos)); + return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); } @DeleteMapping("/request") @@ -67,15 +70,21 @@ public ResponseEntity> deleteFriendRequest(@RequestParam HttpServletRequest request) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.deleteFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success("removed")); + return ResponseEntity.ok() + .body(ApiResponseDto.success(HttpStatus.OK.value(), + String.format("The friend request from '%s' to '%s' has been deleted.", + requestUsername, receiptUsername))); } - @PutMapping("/approve") + @PutMapping("/accept") @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") public ResponseEntity> approveFriendRequest(@RequestParam String requestUsername, HttpServletRequest request) { String receiptUsername = jwtService.extractUsernameFromToken(request).get(); friendService.approveFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponseDto.success("approved")); + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponseDto.success(HttpStatus.CREATED.value(), + String.format("The friend request from User '%s' to User '%s' has been accepted.", + requestUsername, receiptUsername))); } } diff --git a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java index 2c2ad35..4200ffc 100644 --- a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java +++ b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java @@ -89,7 +89,7 @@ public ResponseEntity githubLogin(@RequestParam String code) { // 응답 생성 TokenDto tokenDto = new TokenDto(accessToken, refreshToken); - ApiResponseDto responseDto = ApiResponseDto.success(tokenDto); + ApiResponseDto responseDto = ApiResponseDto.success(HttpStatus.OK.value(), tokenDto); return ResponseEntity.ok() .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) @@ -97,7 +97,8 @@ public ResponseEntity githubLogin(@RequestParam String code) { } catch (Exception e) { log.error("Error during GitHub OAuth process", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ApiResponseDto.failure("An error occurred during the OAuth process")); + .body(ApiResponseDto.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), + "An error occurred during the OAuth process")); } } @@ -112,14 +113,14 @@ public ResponseEntity refreshToken(@RequestBody TokenDto tokenDto) { if (refreshToken.equals(user.get().getRefreshToken())) { String newAccessToken = jwtService.createAccessToken(username); TokenDto newTokenDto = new TokenDto(newAccessToken, refreshToken); - return ResponseEntity.ok(ApiResponseDto.success(newTokenDto)); + return ResponseEntity.ok(ApiResponseDto.success(HttpStatus.OK.value(), newTokenDto)); } } } return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(ApiResponseDto.failure("Invalid refresh token")); + .body(ApiResponseDto.error(HttpStatus.UNAUTHORIZED.value(), "Invalid refresh token")); } private HttpEntity> getAccessToken(String code) { diff --git a/src/main/java/com/server/global/dto/ApiResponseDto.java b/src/main/java/com/server/global/dto/ApiResponseDto.java index 9dcc6f0..07e98ee 100644 --- a/src/main/java/com/server/global/dto/ApiResponseDto.java +++ b/src/main/java/com/server/global/dto/ApiResponseDto.java @@ -1,22 +1,37 @@ package com.server.global.dto; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonInclude; + import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; +import lombok.Builder; +import lombok.Data; -@Getter -@Setter +@Data +@Builder @AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) public class ApiResponseDto { - private boolean success; - private T data; + private int status; private String message; + private T data; + private LocalDateTime timestamp; - public static ApiResponseDto success(T data) { - return new ApiResponseDto<>(true, data, null); + public static ApiResponseDto success(int status, T data) { + return ApiResponseDto.builder() + .status(status) + .message("요청이 성공적으로 처리되었습니다.") + .data(data) + .timestamp(LocalDateTime.now()) + .build(); } - public static ApiResponseDto failure(String message) { - return new ApiResponseDto<>(false, null, message); + public static ApiResponseDto error(int status, String message) { + return ApiResponseDto.builder() + .status(status) + .message(message) + .timestamp(LocalDateTime.now()) + .build(); } } diff --git a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java b/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java index 4bbbc32..c4cba75 100644 --- a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java @@ -14,7 +14,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity> handleFriendException(BusinessException e) { - return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.failure(e.getMessage())); + return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage())); } } From 028f7e29ac3c35faee98df3aba1f22cbb6f71140 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Tue, 29 Oct 2024 12:11:18 +0900 Subject: [PATCH 09/24] :hammer: remove: gradle cache --- k8s/backend.yaml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/k8s/backend.yaml b/k8s/backend.yaml index 2a88640..4a80f1f 100644 --- a/k8s/backend.yaml +++ b/k8s/backend.yaml @@ -58,26 +58,20 @@ spec: secretKeyRef: name: jwt-secret key: JWT_SECRET_KEY - command: - - "scripts/local.sh" # command: - # - "sh" - # - "-c" - # - "tail -f /dev/null" + # - "scripts/local.sh" + command: + - "sh" + - "-c" + - "tail -f /dev/null" volumeMounts: - mountPath: /home/zerohertz/workspace name: backend-storage - - mountPath: /home/zerohertz/.gradle - name: gradle-cache volumes: - name: backend-storage hostPath: path: /home/zerohertz/Zerohertz/co-co-gong-server type: DirectoryOrCreate - - name: gradle-cache - hostPath: - path: /home/zerohertz/.gradle - type: DirectoryOrCreate --- apiVersion: v1 kind: Service From 8edb1c674187dc34a0e95830bb20e927fedf087c Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Tue, 29 Oct 2024 13:35:57 +0900 Subject: [PATCH 10/24] :bento: update: exceptions --- .../friend/controller/FriendController.java | 24 +++---- .../server/domain/friend/entity/Friend.java | 12 ++++ .../domain/friend/enums/FriendState.java | 2 +- .../domain/friend/service/FriendService.java | 69 +++++++++++++++---- .../global/error/code/FriendErrorCode.java | 9 ++- 5 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index 1703a93..7a03af9 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -54,16 +54,6 @@ public ResponseEntity>> getFriendRequest( return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); } - @GetMapping("/receipt") - @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") - public ResponseEntity>> getFriendReceipt( - @RequestParam(required = false) FriendState state, - HttpServletRequest request) { - String username = jwtService.extractUsernameFromToken(request).get(); - List getUserOutDtos = friendService.getReceiptUser(username, state); - return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); - } - @DeleteMapping("/request") @Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제") public ResponseEntity> deleteFriendRequest(@RequestParam String receiptUsername, @@ -76,12 +66,22 @@ public ResponseEntity> deleteFriendRequest(@RequestParam requestUsername, receiptUsername))); } - @PutMapping("/accept") + @GetMapping("/receipt") + @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") + public ResponseEntity>> getFriendReceipt( + @RequestParam(required = false) FriendState state, + HttpServletRequest request) { + String username = jwtService.extractUsernameFromToken(request).get(); + List getUserOutDtos = friendService.getReceiptUser(username, state); + return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); + } + + @PutMapping("/receipt") @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") public ResponseEntity> approveFriendRequest(@RequestParam String requestUsername, HttpServletRequest request) { String receiptUsername = jwtService.extractUsernameFromToken(request).get(); - friendService.approveFriendRequest(requestUsername, receiptUsername); + friendService.acceptFriendRequest(requestUsername, receiptUsername); return ResponseEntity.status(HttpStatus.CREATED) .body(ApiResponseDto.success(HttpStatus.CREATED.value(), String.format("The friend request from User '%s' to User '%s' has been accepted.", diff --git a/src/main/java/com/server/domain/friend/entity/Friend.java b/src/main/java/com/server/domain/friend/entity/Friend.java index 2a6fd7b..5cae7dd 100644 --- a/src/main/java/com/server/domain/friend/entity/Friend.java +++ b/src/main/java/com/server/domain/friend/entity/Friend.java @@ -8,6 +8,8 @@ import com.server.domain.friend.enums.FriendState; import com.server.domain.user.entity.User; +import com.server.global.error.code.FriendErrorCode; +import com.server.global.error.exception.BusinessException; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -19,6 +21,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import lombok.AllArgsConstructor; @@ -63,4 +67,12 @@ public class Friend { @LastModifiedDate private LocalDateTime updatedAt; + @PrePersist + @PreUpdate + private void validateRecursiveRequest() { + if (requestUser.getUsername().equals(receiptUser.getUsername())) { + throw new BusinessException(FriendErrorCode.SELF_FRIEND_REQUEST_NOT_ALLOWED); + } + } + } diff --git a/src/main/java/com/server/domain/friend/enums/FriendState.java b/src/main/java/com/server/domain/friend/enums/FriendState.java index fe0ba2e..aeca3e6 100644 --- a/src/main/java/com/server/domain/friend/enums/FriendState.java +++ b/src/main/java/com/server/domain/friend/enums/FriendState.java @@ -3,5 +3,5 @@ public enum FriendState { SENDING, REMOVED, - APPROVED + ACCEPTED } diff --git a/src/main/java/com/server/domain/friend/service/FriendService.java b/src/main/java/com/server/domain/friend/service/FriendService.java index 1a8d2ad..0d50751 100644 --- a/src/main/java/com/server/domain/friend/service/FriendService.java +++ b/src/main/java/com/server/domain/friend/service/FriendService.java @@ -1,6 +1,7 @@ package com.server.domain.friend.service; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -33,10 +34,10 @@ public List getRequestUser(String username, FriendState state) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); if (state == null) { friends = friendRepository.findByRequestUser(user) - .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND)); } else { friends = friendRepository.findByRequestUserAndState(user, state) - .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND)); } List getFriendOutDtos = friends.stream() .map(friend -> friendMapper.toGetFriendOutDto(friend.getReceiptUser(), friend.getState())) @@ -50,10 +51,10 @@ public List getReceiptUser(String username, FriendState state) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); if (state == null) { friends = friendRepository.findByReceiptUser(user) - .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND)); } else { friends = friendRepository.findByReceiptUserAndState(user, state) - .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND)); } List getFriendOutDtos = friends.stream() .map(friend -> friendMapper.toGetFriendOutDto(friend.getRequestUser(), friend.getState())) @@ -61,12 +62,35 @@ public List getReceiptUser(String username, FriendState state) return getFriendOutDtos; } + private void validateFriendRequest(User user1, User user2) { + Optional friend; + friend = friendRepository.findByRequestUserAndReceiptUser(user1, user2); + if (friend.isPresent()) { + if (FriendState.SENDING == friend.get().getState()) { + throw new BusinessException(FriendErrorCode.REQUEST_ALREADY_EXISTS); + } + if (FriendState.ACCEPTED == friend.get().getState()) { + throw new BusinessException(FriendErrorCode.FRIENDS_ALREADY_EXISTS); + } + } + friend = friendRepository.findByRequestUserAndReceiptUser(user2, user1); + if (friend.isPresent()) { + if (FriendState.SENDING == friend.get().getState()) { + throw new BusinessException(FriendErrorCode.RECEIPT_ALREADY_EXISTS); + } + if (FriendState.ACCEPTED == friend.get().getState()) { + throw new BusinessException(FriendErrorCode.FRIENDS_ALREADY_EXISTS); + } + } + } + @Transactional public Friend createFriendRequest(String requestUsername, String receiptUsername) { User requestUser = userRepository.findByUsername(requestUsername) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); User receiptUser = userRepository.findByUsername(receiptUsername) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + validateFriendRequest(requestUser, receiptUser); Friend friend = Friend.builder() .requestUser(requestUser) .receiptUser(receiptUser) @@ -82,24 +106,41 @@ public void deleteFriendRequest(String requestUsername, String receiptUsername) User receiptUser = userRepository.findByUsername(receiptUsername) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); Friend friend = friendRepository.findByRequestUserAndReceiptUser(requestUser, receiptUser) - .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); + .orElseThrow(() -> new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND)); + if (FriendState.ACCEPTED == friend.getState()) { + throw new BusinessException(FriendErrorCode.FRIENDS_ALREADY_EXISTS); + } + if (FriendState.REMOVED == friend.getState()) { + throw new BusinessException(FriendErrorCode.REQUEST_ALREADY_REMOVED); + } friend.setState(FriendState.REMOVED); } @Transactional - public void approveFriendRequest(String requestUsername, String receiptUsername) { + public void acceptFriendRequest(String requestUsername, String receiptUsername) { User requestUser = userRepository.findByUsername(requestUsername) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); User receiptUser = userRepository.findByUsername(receiptUsername) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); Friend friendRequested = friendRepository.findByRequestUserAndReceiptUser(requestUser, receiptUser) - .orElseThrow(() -> new BusinessException(FriendErrorCode.NOT_FOUND)); - friendRequested.setState(FriendState.APPROVED); - Friend friendApproved = Friend.builder() - .requestUser(receiptUser) - .receiptUser(requestUser) - .state(FriendState.APPROVED) - .build(); - friendRepository.save(friendApproved); + .orElseThrow(() -> new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND)); + if (FriendState.REMOVED == friendRequested.getState()) { + throw new BusinessException(FriendErrorCode.REQUEST_NOT_FOUND); + } + if (FriendState.ACCEPTED == friendRequested.getState()) { + throw new BusinessException(FriendErrorCode.FRIENDS_ALREADY_EXISTS); + } + friendRequested.setState(FriendState.ACCEPTED); + Optional friendReceipted = friendRepository.findByRequestUserAndReceiptUser(receiptUser, requestUser); + if (friendReceipted.isPresent()) { + friendReceipted.get().setState(FriendState.ACCEPTED); + } else { + Friend friendApproved = Friend.builder() + .requestUser(receiptUser) + .receiptUser(requestUser) + .state(FriendState.ACCEPTED) + .build(); + friendRepository.save(friendApproved); + } } } diff --git a/src/main/java/com/server/global/error/code/FriendErrorCode.java b/src/main/java/com/server/global/error/code/FriendErrorCode.java index 6b45425..96d6db0 100644 --- a/src/main/java/com/server/global/error/code/FriendErrorCode.java +++ b/src/main/java/com/server/global/error/code/FriendErrorCode.java @@ -8,9 +8,12 @@ @Getter @AllArgsConstructor public enum FriendErrorCode implements ErrorCode { - ALREADY_FRIENDS(HttpStatus.CONFLICT.value(), "이미 친구 상태입니다."), - ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "친구 신청을 이미 완료했습니다."), - NOT_FOUND(HttpStatus.NOT_FOUND.value(), "친구 신청이 존재하지 않습니다."); + FRIENDS_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 친구 상태입니다."), + REQUEST_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "친구 신청을 이미 완료했습니다."), + REQUEST_ALREADY_REMOVED(HttpStatus.GONE.value(), "이미 삭제된 친구 신청입니다."), + RECEIPT_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "해당 사용자에게서 이미 친구 신청을 받은 상태입니다."), + SELF_FRIEND_REQUEST_NOT_ALLOWED(HttpStatus.BAD_REQUEST.value(), "자기 자신을 친구로 추가할 수 없습니다."), + REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "친구 신청이 존재하지 않습니다."); private final int status; private final String message; From 3c60001de51c51e213104312e02e6a3b5845182c Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Tue, 29 Oct 2024 14:41:56 +0900 Subject: [PATCH 11/24] :sparkles: feat: auth exception (/api/users/me) --- .../user/controller/UserController.java | 24 ++++++++++--------- .../global/error/code/AuthErrorCode.java | 15 ++++++++++++ .../global/error/exception/AuthException.java | 12 ++++++++++ .../exception/GlobalExceptionHandler.java | 5 ++++ 4 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/server/global/error/code/AuthErrorCode.java create mode 100644 src/main/java/com/server/global/error/exception/AuthException.java diff --git a/src/main/java/com/server/domain/user/controller/UserController.java b/src/main/java/com/server/domain/user/controller/UserController.java index 4784299..81d5e0e 100644 --- a/src/main/java/com/server/domain/user/controller/UserController.java +++ b/src/main/java/com/server/domain/user/controller/UserController.java @@ -13,6 +13,11 @@ import com.server.domain.user.entity.User; import com.server.domain.user.service.UserService; +import com.server.global.dto.ApiResponseDto; +import com.server.global.error.code.AuthErrorCode; +import com.server.global.error.code.UserErrorCode; +import com.server.global.error.exception.AuthException; +import com.server.global.error.exception.BusinessException; import com.server.global.jwt.JwtService; import jakarta.servlet.http.HttpServletRequest; @@ -30,23 +35,20 @@ public class UserController { // 내 정보 @GetMapping("/me") - public ResponseEntity getUser(HttpServletRequest request) { + public ResponseEntity> getUser(HttpServletRequest request) { // 디버깅을 위한 로그 추가 log.info("Received request to /me endpoint"); // request(token)에서 username 추출 - Optional username = jwtService.extractUsernameFromToken(request); - if (username.isEmpty()) { - log.warn("Failed to extract username from token"); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); - } - - log.info("Extracted username: {}", username.get()); + String username = jwtService.extractUsernameFromToken(request) + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_TOKEN)); + log.info("Extracted username: {}", username); // username으로 찾은 user 반환 - Optional user = userService.findByUsername(username.get()); + User user = userService.findByUsername(username) + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); - return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), user)); } @GetMapping("/{username}") @@ -79,7 +81,7 @@ public ResponseEntity updateUser(HttpServletRequest request, String email) { public ResponseEntity deleteUser(HttpServletRequest request) { Optional username = jwtService.extractUsernameFromToken(request); if (username.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); + throw new AuthException(AuthErrorCode.INVALID_TOKEN); } // username으로 찾은 user 반환 diff --git a/src/main/java/com/server/global/error/code/AuthErrorCode.java b/src/main/java/com/server/global/error/code/AuthErrorCode.java new file mode 100644 index 0000000..9ae4b3f --- /dev/null +++ b/src/main/java/com/server/global/error/code/AuthErrorCode.java @@ -0,0 +1,15 @@ +package com.server.global.error.code; + +import org.springframework.http.HttpStatus; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum AuthErrorCode implements ErrorCode { + INVALID_TOKEN(HttpStatus.UNAUTHORIZED.value(), "유효하지 않은 토큰입니다."); + + private final int status; + private final String message; +} diff --git a/src/main/java/com/server/global/error/exception/AuthException.java b/src/main/java/com/server/global/error/exception/AuthException.java new file mode 100644 index 0000000..1e6c052 --- /dev/null +++ b/src/main/java/com/server/global/error/exception/AuthException.java @@ -0,0 +1,12 @@ +package com.server.global.error.exception; + +import com.server.global.error.code.ErrorCode; + +import lombok.Getter; + +@Getter +public class AuthException extends BaseException { + public AuthException(final ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java b/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java index c4cba75..0fa0ac7 100644 --- a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java @@ -12,6 +12,11 @@ @RestControllerAdvice public class GlobalExceptionHandler { + @ExceptionHandler(AuthException.class) + public ResponseEntity> handleFriendException(AuthException e) { + return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage())); + } + @ExceptionHandler(BusinessException.class) public ResponseEntity> handleFriendException(BusinessException e) { return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage())); From f9640c1b3af462e9082aada11a2fc9394ea9dc6b Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Tue, 29 Oct 2024 15:07:02 +0900 Subject: [PATCH 12/24] :recycle: refactor: exceptions (user controller) --- .../user/controller/UserController.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/server/domain/user/controller/UserController.java b/src/main/java/com/server/domain/user/controller/UserController.java index 81d5e0e..a74fbcd 100644 --- a/src/main/java/com/server/domain/user/controller/UserController.java +++ b/src/main/java/com/server/domain/user/controller/UserController.java @@ -1,7 +1,5 @@ package com.server.domain.user.controller; -import java.util.Optional; - import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -33,6 +31,10 @@ public class UserController { private final JwtService jwtService; private final UserService userService; + // TODO:: throw INVALID_TOKEN (related: FriendController) + // jwtService layer? or Controller layer? + // NOTE: jwtService layer에서 에러 핸들링을 수행하는 것이 좋을듯 + // 내 정보 @GetMapping("/me") public ResponseEntity> getUser(HttpServletRequest request) { @@ -52,43 +54,41 @@ public ResponseEntity> getUser(HttpServletRequest request) } @GetMapping("/{username}") - public ResponseEntity getUserByUsername(@PathVariable String username) { - Optional user = userService.findByUsername(username); - return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + public ResponseEntity> getUserByUsername(@PathVariable String username) { + User user = userService.findByUsername(username) + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), user)); } // 정보 수정 // 현재 바꿀 수 있는 거 email. 추후 닉네임 추가..? @PutMapping - public ResponseEntity updateUser(HttpServletRequest request, String email) { - Optional username = jwtService.extractUsernameFromToken(request); - if (username.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or missing token"); - } + public ResponseEntity> updateUser(HttpServletRequest request, String email) { + String username = jwtService.extractUsernameFromToken(request) + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_TOKEN)); // username으로 찾은 user 반환 - Optional user = userService.findByUsername(username.get()); - - return user.map(value -> { - User changedUser = userService.updateEmail(value, email); - return ResponseEntity.ok(changedUser.getEmail()); - }).orElseGet(() -> ResponseEntity.notFound().build()); + User user = userService.findByUsername(username) + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + User changedUser = userService.updateEmail(user, email); + // TODO: HTTP Status Code? + // PUT -> 201? + return ResponseEntity.ok() + .body(ApiResponseDto.success(HttpStatus.OK.value(), changedUser.getEmail())); } // 사용자 탈퇴 @DeleteMapping("/me") - public ResponseEntity deleteUser(HttpServletRequest request) { - Optional username = jwtService.extractUsernameFromToken(request); - if (username.isEmpty()) { - throw new AuthException(AuthErrorCode.INVALID_TOKEN); - } + public ResponseEntity> deleteUser(HttpServletRequest request) { + String username = jwtService.extractUsernameFromToken(request) + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_TOKEN)); // username으로 찾은 user 반환 - Optional user = userService.findByUsername(username.get()); - return user.map(value -> { - userService.deleteUser(value); - return ResponseEntity.ok("Success delete user " + value.getUsername()); - }).orElseGet(() -> ResponseEntity.notFound().build()); + User user = userService.findByUsername(username) + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + return ResponseEntity.ok() + .body(ApiResponseDto.success(HttpStatus.OK.value(), + String.format("Success delete user: %s", user.getUsername()))); } } From b8ec493a940ac6c15a0183793bb9c8cbb7fe053c Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Tue, 29 Oct 2024 16:38:10 +0900 Subject: [PATCH 13/24] :recycle: refactor: response status annotation --- .../friend/controller/FriendController.java | 40 +++++++++---------- .../user/controller/UserController.java | 34 ++++++++-------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index 7a03af9..baa12b4 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -3,13 +3,13 @@ import java.util.List; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.server.domain.friend.dto.GetFriendOutDto; @@ -32,59 +32,59 @@ public class FriendController { private final JwtService jwtService; private final FriendService friendService; + @ResponseStatus(HttpStatus.CREATED) @PostMapping("/request") @Operation(summary = "친구 신청 생성", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 생성") - public ResponseEntity> createFriendRequest(@RequestParam String receiptUsername, + public ApiResponseDto createFriendRequest(@RequestParam String receiptUsername, HttpServletRequest request) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.createFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.CREATED) - .body(ApiResponseDto.success(HttpStatus.CREATED.value(), - String.format("A friend request from User '%s' to User '%s' has been created.", - requestUsername, receiptUsername))); + return ApiResponseDto.success(HttpStatus.CREATED.value(), + String.format("A friend request from User '%s' to User '%s' has been created.", + requestUsername, receiptUsername)); } + @ResponseStatus(HttpStatus.OK) @GetMapping("/request") @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 보낸 내역") - public ResponseEntity>> getFriendRequest( + public ApiResponseDto> getFriendRequest( @RequestParam(required = false) FriendState state, HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getRequestUser(username, state); - return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); + return ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos); } + @ResponseStatus(HttpStatus.OK) @DeleteMapping("/request") @Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제") - public ResponseEntity> deleteFriendRequest(@RequestParam String receiptUsername, + public ApiResponseDto deleteFriendRequest(@RequestParam String receiptUsername, HttpServletRequest request) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.deleteFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.ok() - .body(ApiResponseDto.success(HttpStatus.OK.value(), - String.format("The friend request from '%s' to '%s' has been deleted.", - requestUsername, receiptUsername))); + return ApiResponseDto.success(HttpStatus.OK.value(), String + .format("The friend request from '%s' to '%s' has been deleted.", requestUsername, receiptUsername)); } + @ResponseStatus(HttpStatus.OK) @GetMapping("/receipt") @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") - public ResponseEntity>> getFriendReceipt( + public ApiResponseDto> getFriendReceipt( @RequestParam(required = false) FriendState state, HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getReceiptUser(username, state); - return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos)); + return ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos); } + @ResponseStatus(HttpStatus.CREATED) @PutMapping("/receipt") @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") - public ResponseEntity> approveFriendRequest(@RequestParam String requestUsername, + public ApiResponseDto approveFriendRequest(@RequestParam String requestUsername, HttpServletRequest request) { String receiptUsername = jwtService.extractUsernameFromToken(request).get(); friendService.acceptFriendRequest(requestUsername, receiptUsername); - return ResponseEntity.status(HttpStatus.CREATED) - .body(ApiResponseDto.success(HttpStatus.CREATED.value(), - String.format("The friend request from User '%s' to User '%s' has been accepted.", - requestUsername, receiptUsername))); + return ApiResponseDto.success(HttpStatus.CREATED.value(), String.format( + "The friend request from User '%s' to User '%s' has been accepted.", requestUsername, receiptUsername)); } } diff --git a/src/main/java/com/server/domain/user/controller/UserController.java b/src/main/java/com/server/domain/user/controller/UserController.java index a74fbcd..b665d63 100644 --- a/src/main/java/com/server/domain/user/controller/UserController.java +++ b/src/main/java/com/server/domain/user/controller/UserController.java @@ -1,12 +1,12 @@ package com.server.domain.user.controller; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; 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.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.server.domain.user.entity.User; @@ -31,41 +31,44 @@ public class UserController { private final JwtService jwtService; private final UserService userService; - // TODO:: throw INVALID_TOKEN (related: FriendController) + // TODO:: throw INVALID_ACCESS_TOKEN (related: FriendController) // jwtService layer? or Controller layer? // NOTE: jwtService layer에서 에러 핸들링을 수행하는 것이 좋을듯 // 내 정보 + @ResponseStatus(HttpStatus.OK) @GetMapping("/me") - public ResponseEntity> getUser(HttpServletRequest request) { + public ApiResponseDto getUser(HttpServletRequest request) { // 디버깅을 위한 로그 추가 log.info("Received request to /me endpoint"); // request(token)에서 username 추출 String username = jwtService.extractUsernameFromToken(request) - .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_TOKEN)); + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); log.info("Extracted username: {}", username); // username으로 찾은 user 반환 User user = userService.findByUsername(username) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); - return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), user)); + return ApiResponseDto.success(HttpStatus.OK.value(), user); } + @ResponseStatus(HttpStatus.OK) @GetMapping("/{username}") - public ResponseEntity> getUserByUsername(@PathVariable String username) { + public ApiResponseDto getUserByUsername(@PathVariable String username) { User user = userService.findByUsername(username) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); - return ResponseEntity.ok().body(ApiResponseDto.success(HttpStatus.OK.value(), user)); + return ApiResponseDto.success(HttpStatus.OK.value(), user); } // 정보 수정 // 현재 바꿀 수 있는 거 email. 추후 닉네임 추가..? + @ResponseStatus(HttpStatus.OK) @PutMapping - public ResponseEntity> updateUser(HttpServletRequest request, String email) { + public ApiResponseDto updateUser(HttpServletRequest request, String email) { String username = jwtService.extractUsernameFromToken(request) - .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_TOKEN)); + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); // username으로 찾은 user 반환 User user = userService.findByUsername(username) @@ -74,21 +77,20 @@ public ResponseEntity> updateUser(HttpServletRequest requ // TODO: HTTP Status Code? // PUT -> 201? - return ResponseEntity.ok() - .body(ApiResponseDto.success(HttpStatus.OK.value(), changedUser.getEmail())); + return ApiResponseDto.success(HttpStatus.OK.value(), changedUser.getEmail()); } // 사용자 탈퇴 + @ResponseStatus(HttpStatus.OK) @DeleteMapping("/me") - public ResponseEntity> deleteUser(HttpServletRequest request) { + public ApiResponseDto deleteUser(HttpServletRequest request) { String username = jwtService.extractUsernameFromToken(request) - .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_TOKEN)); + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); // username으로 찾은 user 반환 User user = userService.findByUsername(username) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); - return ResponseEntity.ok() - .body(ApiResponseDto.success(HttpStatus.OK.value(), - String.format("Success delete user: %s", user.getUsername()))); + return ApiResponseDto.success(HttpStatus.OK.value(), + String.format("Success delete user: %s", user.getUsername())); } } From fabccaeef15cf10d9404b916de26613601517874 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Wed, 30 Oct 2024 22:28:53 +0900 Subject: [PATCH 14/24] :bento: chore: local build failed --- scripts/local.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/local.sh b/scripts/local.sh index bcc9e7b..b4375b9 100755 --- a/scripts/local.sh +++ b/scripts/local.sh @@ -2,5 +2,10 @@ rm -r build -./gradlew clean build +if ./gradlew clean build; then + echo "Build success!" +else + echo "Build failed..." + exit 1 +fi java -jar build/libs/*SNAPSHOT.jar From 56b5317ff9e8936aab696749fe4f09116db0e0d7 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Wed, 30 Oct 2024 22:30:49 +0900 Subject: [PATCH 15/24] :bug: fix: delete user --- .../domain/user/controller/UserController.java | 2 ++ .../java/com/server/domain/user/entity/User.java | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/com/server/domain/user/controller/UserController.java b/src/main/java/com/server/domain/user/controller/UserController.java index b665d63..5a102e7 100644 --- a/src/main/java/com/server/domain/user/controller/UserController.java +++ b/src/main/java/com/server/domain/user/controller/UserController.java @@ -90,6 +90,8 @@ public ApiResponseDto deleteUser(HttpServletRequest request) { // username으로 찾은 user 반환 User user = userService.findByUsername(username) .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + + userService.deleteUser(user); return ApiResponseDto.success(HttpStatus.OK.value(), String.format("Success delete user: %s", user.getUsername())); } diff --git a/src/main/java/com/server/domain/user/entity/User.java b/src/main/java/com/server/domain/user/entity/User.java index 5148ad7..3b6a1b0 100644 --- a/src/main/java/com/server/domain/user/entity/User.java +++ b/src/main/java/com/server/domain/user/entity/User.java @@ -1,17 +1,23 @@ package com.server.domain.user.entity; import java.time.LocalDateTime; +import java.util.List; import org.hibernate.annotations.CreationTimestamp; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import com.server.domain.friend.entity.Friend; + +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; @@ -57,6 +63,12 @@ public class User { @Column(name = "refresh_token", length = 512) private String refreshToken; + @OneToMany(mappedBy = "requestUser", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) + private List requestUserId; + + @OneToMany(mappedBy = "receiptUser", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) + private List receiptUser; + @Builder public User(String username, String thumbnail, String email, String oauth, String githubToken) { this.username = username; From eb40f301244ed5f47e73bf03e857e72a4c1cfc66 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Wed, 30 Oct 2024 23:01:18 +0900 Subject: [PATCH 16/24] :bug: fix: authorize http requests --- .../server/global/config/SecurityConfig.java | 21 +++++++------------ .../global/error/code/AuthErrorCode.java | 5 ++++- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/server/global/config/SecurityConfig.java b/src/main/java/com/server/global/config/SecurityConfig.java index 91194d1..9e369b7 100644 --- a/src/main/java/com/server/global/config/SecurityConfig.java +++ b/src/main/java/com/server/global/config/SecurityConfig.java @@ -8,11 +8,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.Arrays; @Configuration @EnableWebSecurity @@ -23,17 +18,17 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth - .requestMatchers("/login", "/login/**", "/githubLogin/**").permitAll() // 수정된 부분 - .requestMatchers("/", "/css/**", "/images/**", "/js/**", "/favicon.ico").permitAll() - .requestMatchers("/api/**", "/api-docs/**", "/swagger/**", "/swagger-ui/**").permitAll() - .requestMatchers("/auth/**", "/oauth2/**", "/signaling/**").permitAll() - .anyRequest().authenticated() - ) + // Auth + .requestMatchers("/login", "/login/**", "/refresh").permitAll() + // API + .requestMatchers("/api/**").permitAll() + // Swagger + .requestMatchers("/api-docs/**", "/swagger-ui/**", "/swagger/**").permitAll() + .anyRequest().authenticated()) .csrf(csrf -> csrf.disable()) .httpBasic(httpBasic -> httpBasic.disable()) .formLogin(form -> form.disable()) @@ -41,4 +36,4 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/server/global/error/code/AuthErrorCode.java b/src/main/java/com/server/global/error/code/AuthErrorCode.java index 9ae4b3f..2bd1200 100644 --- a/src/main/java/com/server/global/error/code/AuthErrorCode.java +++ b/src/main/java/com/server/global/error/code/AuthErrorCode.java @@ -8,7 +8,10 @@ @Getter @AllArgsConstructor public enum AuthErrorCode implements ErrorCode { - INVALID_TOKEN(HttpStatus.UNAUTHORIZED.value(), "유효하지 않은 토큰입니다."); + URL_NOT_PERMITTED(HttpStatus.FORBIDDEN.value(), "허용되지 않은 URL입니다."), + OAUTH_PROCESS_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "OAuth 과정 중 오류가 발생했습니다."), + INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED.value(), "유효하지 않은 access token입니다."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "유효하지 않은 refresh token입니다."); private final int status; private final String message; From 6c761b0bff5e6e808af23ba515b18666be719ba5 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Wed, 30 Oct 2024 23:09:07 +0900 Subject: [PATCH 17/24] :recycle: refactor: oauth --- .../controller/OAuthLoginController.java | 66 ++++++++++--------- .../java/com/server/global/dto/TokenDto.java | 7 ++ 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java index 4200ffc..ca39b92 100644 --- a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java +++ b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java @@ -1,7 +1,5 @@ package com.server.domain.oauth.controller; -import java.util.Optional; - import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -15,6 +13,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @@ -24,8 +23,13 @@ import com.server.domain.user.service.UserService; import com.server.global.dto.ApiResponseDto; import com.server.global.dto.TokenDto; +import com.server.global.error.code.AuthErrorCode; +import com.server.global.error.code.UserErrorCode; +import com.server.global.error.exception.AuthException; +import com.server.global.error.exception.BusinessException; import com.server.global.jwt.JwtService; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -45,27 +49,29 @@ public class OAuthLoginController { private String clientSecret; // 새로 추가된 로그인 시작점 + @ResponseStatus(HttpStatus.FOUND) @GetMapping("/login") - public ResponseEntity login() { - String githubAuthUrl = String.format("%s%s%s%s%s", "https://github.com/login/oauth/authorize?client_id=", - clientId, "&redirect_uri=", redirectUri, "/login/oauth2/code/github"); - return ResponseEntity.status(HttpStatus.FOUND) - .header(HttpHeaders.LOCATION, githubAuthUrl) - .build(); + public ApiResponseDto login(HttpServletResponse response) { + String githubAuthUrl = String.format( + "https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s/login/oauth2/code/github", + clientId, redirectUri); + response.setHeader(HttpHeaders.LOCATION, githubAuthUrl); + return ApiResponseDto.success(HttpStatus.FOUND.value(), "Login Success"); } + @ResponseStatus(HttpStatus.OK) @GetMapping("/login/oauth2/code/github") - public ResponseEntity githubLogin(@RequestParam String code) { + public ApiResponseDto githubLogin(HttpServletResponse response, @RequestParam String code) { try { RestTemplate restTemplate = new RestTemplate(); // GitHub 액세스 토큰 요청 - ResponseEntity response = restTemplate.exchange( + ResponseEntity githubAccessTokenResponse = restTemplate.exchange( "https://github.com/login/oauth/access_token", HttpMethod.POST, getAccessToken(code), OAuthInfo.class); - String githubAccessToken = response.getBody().getAccessToken(); + String githubAccessToken = githubAccessTokenResponse.getBody().getAccessToken(); // GitHub 사용자 정보 요청 ResponseEntity userInfoResponse = restTemplate.exchange( @@ -89,38 +95,36 @@ public ResponseEntity githubLogin(@RequestParam String code) { // 응답 생성 TokenDto tokenDto = new TokenDto(accessToken, refreshToken); - ApiResponseDto responseDto = ApiResponseDto.success(HttpStatus.OK.value(), tokenDto); - return ResponseEntity.ok() - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) - .body(responseDto); + response.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); + return ApiResponseDto.success(HttpStatus.OK.value(), tokenDto); } catch (Exception e) { log.error("Error during GitHub OAuth process", e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ApiResponseDto.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), - "An error occurred during the OAuth process")); + throw new AuthException(AuthErrorCode.OAUTH_PROCESS_ERROR); } } + @ResponseStatus(HttpStatus.OK) @PostMapping("/refresh") - public ResponseEntity refreshToken(@RequestBody TokenDto tokenDto) { + public ApiResponseDto refreshToken(@RequestBody TokenDto tokenDto) { String refreshToken = tokenDto.getRefreshToken(); + log.info(refreshToken); + + if (!jwtService.validateToken(refreshToken)) { + throw new AuthException(AuthErrorCode.INVALID_REFRESH_TOKEN); + } - if (jwtService.validateToken(refreshToken)) { - String username = jwtService.extractUsername(refreshToken).get(); - Optional user = userService.findByUsername(username); - if (user.isPresent()) { - if (refreshToken.equals(user.get().getRefreshToken())) { - String newAccessToken = jwtService.createAccessToken(username); - TokenDto newTokenDto = new TokenDto(newAccessToken, refreshToken); - return ResponseEntity.ok(ApiResponseDto.success(HttpStatus.OK.value(), newTokenDto)); - } - } + String username = jwtService.extractUsername(refreshToken).get(); + User user = userService.findByUsername(username) + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + if (!refreshToken.equals(user.getRefreshToken())) { + throw new AuthException(AuthErrorCode.INVALID_REFRESH_TOKEN); } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(ApiResponseDto.error(HttpStatus.UNAUTHORIZED.value(), "Invalid refresh token")); + String newAccessToken = jwtService.createAccessToken(username); + TokenDto newTokenDto = new TokenDto(newAccessToken, refreshToken); + return ApiResponseDto.success(HttpStatus.OK.value(), newTokenDto); } private HttpEntity> getAccessToken(String code) { diff --git a/src/main/java/com/server/global/dto/TokenDto.java b/src/main/java/com/server/global/dto/TokenDto.java index 040073c..47c46ca 100644 --- a/src/main/java/com/server/global/dto/TokenDto.java +++ b/src/main/java/com/server/global/dto/TokenDto.java @@ -1,5 +1,6 @@ package com.server.global.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -7,7 +8,13 @@ @Getter @AllArgsConstructor @NoArgsConstructor +@Schema(description = "Token data transfer object") public class TokenDto { + + @Schema(description = "Access token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...") private String accessToken; + + @Schema(description = "Refresh token", example = "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...") private String refreshToken; + } From d5b748524d70f2f741a34c21f86033397fa094ff Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:01:42 +0900 Subject: [PATCH 18/24] :warning: secure: get other users info --- .../oauth/controller/OAuthLoginController.java | 5 +---- .../domain/user/controller/UserController.java | 17 ++++++----------- .../server/domain/user/dto/GetUserOutDto.java | 9 +++++++++ .../com/server/domain/user/entity/User.java | 2 +- .../server/domain/user/mapper/UserMapper.java | 15 +++++++++++++++ .../server/domain/user/service/UserService.java | 17 +++++++++++++---- 6 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/server/domain/user/dto/GetUserOutDto.java create mode 100644 src/main/java/com/server/domain/user/mapper/UserMapper.java diff --git a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java index ca39b92..ef97096 100644 --- a/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java +++ b/src/main/java/com/server/domain/oauth/controller/OAuthLoginController.java @@ -24,9 +24,7 @@ import com.server.global.dto.ApiResponseDto; import com.server.global.dto.TokenDto; import com.server.global.error.code.AuthErrorCode; -import com.server.global.error.code.UserErrorCode; import com.server.global.error.exception.AuthException; -import com.server.global.error.exception.BusinessException; import com.server.global.jwt.JwtService; import jakarta.servlet.http.HttpServletResponse; @@ -115,8 +113,7 @@ public ApiResponseDto refreshToken(@RequestBody TokenDto tokenDto) { } String username = jwtService.extractUsername(refreshToken).get(); - User user = userService.findByUsername(username) - .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + User user = userService.getUserWithPersonalInfo(username); if (!refreshToken.equals(user.getRefreshToken())) { throw new AuthException(AuthErrorCode.INVALID_REFRESH_TOKEN); diff --git a/src/main/java/com/server/domain/user/controller/UserController.java b/src/main/java/com/server/domain/user/controller/UserController.java index 5a102e7..fa50290 100644 --- a/src/main/java/com/server/domain/user/controller/UserController.java +++ b/src/main/java/com/server/domain/user/controller/UserController.java @@ -9,13 +9,12 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import com.server.domain.user.dto.GetUserOutDto; import com.server.domain.user.entity.User; import com.server.domain.user.service.UserService; import com.server.global.dto.ApiResponseDto; import com.server.global.error.code.AuthErrorCode; -import com.server.global.error.code.UserErrorCode; import com.server.global.error.exception.AuthException; -import com.server.global.error.exception.BusinessException; import com.server.global.jwt.JwtService; import jakarta.servlet.http.HttpServletRequest; @@ -48,17 +47,15 @@ public ApiResponseDto getUser(HttpServletRequest request) { log.info("Extracted username: {}", username); // username으로 찾은 user 반환 - User user = userService.findByUsername(username) - .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + User user = userService.getUserWithPersonalInfo(username); return ApiResponseDto.success(HttpStatus.OK.value(), user); } @ResponseStatus(HttpStatus.OK) @GetMapping("/{username}") - public ApiResponseDto getUserByUsername(@PathVariable String username) { - User user = userService.findByUsername(username) - .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + public ApiResponseDto getUserByUsername(@PathVariable String username) { + GetUserOutDto user = userService.getUserWithoutPersonalInfo(username); return ApiResponseDto.success(HttpStatus.OK.value(), user); } @@ -71,8 +68,7 @@ public ApiResponseDto updateUser(HttpServletRequest request, String emai .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); // username으로 찾은 user 반환 - User user = userService.findByUsername(username) - .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + User user = userService.getUserWithPersonalInfo(username); User changedUser = userService.updateEmail(user, email); // TODO: HTTP Status Code? @@ -88,8 +84,7 @@ public ApiResponseDto deleteUser(HttpServletRequest request) { .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); // username으로 찾은 user 반환 - User user = userService.findByUsername(username) - .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + User user = userService.getUserWithPersonalInfo(username); userService.deleteUser(user); return ApiResponseDto.success(HttpStatus.OK.value(), diff --git a/src/main/java/com/server/domain/user/dto/GetUserOutDto.java b/src/main/java/com/server/domain/user/dto/GetUserOutDto.java new file mode 100644 index 0000000..5356c88 --- /dev/null +++ b/src/main/java/com/server/domain/user/dto/GetUserOutDto.java @@ -0,0 +1,9 @@ +package com.server.domain.user.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetUserOutDto extends UserDto { +} diff --git a/src/main/java/com/server/domain/user/entity/User.java b/src/main/java/com/server/domain/user/entity/User.java index 3b6a1b0..8ade545 100644 --- a/src/main/java/com/server/domain/user/entity/User.java +++ b/src/main/java/com/server/domain/user/entity/User.java @@ -64,7 +64,7 @@ public class User { private String refreshToken; @OneToMany(mappedBy = "requestUser", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) - private List requestUserId; + private List requestUser; @OneToMany(mappedBy = "receiptUser", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) private List receiptUser; diff --git a/src/main/java/com/server/domain/user/mapper/UserMapper.java b/src/main/java/com/server/domain/user/mapper/UserMapper.java new file mode 100644 index 0000000..86bbe7b --- /dev/null +++ b/src/main/java/com/server/domain/user/mapper/UserMapper.java @@ -0,0 +1,15 @@ +package com.server.domain.user.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import com.server.domain.user.dto.GetUserOutDto; +import com.server.domain.user.entity.User; + +@Mapper(componentModel = "spring") +public interface UserMapper { + + UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); + + GetUserOutDto toGetUserOutDto(User user); +} diff --git a/src/main/java/com/server/domain/user/service/UserService.java b/src/main/java/com/server/domain/user/service/UserService.java index 83dd08f..273afc5 100644 --- a/src/main/java/com/server/domain/user/service/UserService.java +++ b/src/main/java/com/server/domain/user/service/UserService.java @@ -1,14 +1,16 @@ package com.server.domain.user.service; -import java.util.Optional; - import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.server.domain.oauth.dto.GithubDto; +import com.server.domain.user.dto.GetUserOutDto; import com.server.domain.user.entity.User; +import com.server.domain.user.mapper.UserMapper; import com.server.domain.user.repository.UserRepository; +import com.server.global.error.code.UserErrorCode; +import com.server.global.error.exception.BusinessException; import lombok.RequiredArgsConstructor; @@ -17,6 +19,7 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final UserMapper userMapper; @Transactional public User loginOrRegister(GithubDto githubDto, String githubToken) { @@ -44,8 +47,14 @@ public void saveRefreshToken(String username, String refreshToken) { userRepository.save(user); } - public Optional findByUsername(String username) { - return userRepository.findByUsername(username); + public User getUserWithPersonalInfo(String username) { + return userRepository.findByUsername(username) + .orElseThrow(() -> new BusinessException(UserErrorCode.NOT_FOUND)); + } + + public GetUserOutDto getUserWithoutPersonalInfo(String username) { + User user = getUserWithPersonalInfo(username); + return userMapper.toGetUserOutDto(user); } public User updateEmail(User user, String email) { From 9cf38d1b9bc945add897ee0dab8b02ff9a4e1d00 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:07:07 +0900 Subject: [PATCH 19/24] :hammer: fix: json serialization (foreign key) --- .../java/com/server/domain/friend/service/FriendService.java | 2 ++ src/main/java/com/server/domain/user/entity/User.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/server/domain/friend/service/FriendService.java b/src/main/java/com/server/domain/friend/service/FriendService.java index 0d50751..dcb4b26 100644 --- a/src/main/java/com/server/domain/friend/service/FriendService.java +++ b/src/main/java/com/server/domain/friend/service/FriendService.java @@ -24,6 +24,8 @@ @RequiredArgsConstructor public class FriendService { + // TODO: user.getRequestUser() 또는 user.getReceiptUser()를 통해 refactoring + private final UserRepository userRepository; private final FriendRepository friendRepository; private final FriendMapper friendMapper; diff --git a/src/main/java/com/server/domain/user/entity/User.java b/src/main/java/com/server/domain/user/entity/User.java index 8ade545..52d29da 100644 --- a/src/main/java/com/server/domain/user/entity/User.java +++ b/src/main/java/com/server/domain/user/entity/User.java @@ -7,6 +7,7 @@ import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.server.domain.friend.entity.Friend; import jakarta.persistence.CascadeType; @@ -64,9 +65,11 @@ public class User { private String refreshToken; @OneToMany(mappedBy = "requestUser", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) + @JsonIgnore private List requestUser; @OneToMany(mappedBy = "receiptUser", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) + @JsonIgnore private List receiptUser; @Builder From 51ddf7c231ab31c2e9f6a4d147bf03583d8f069f Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:13:34 +0900 Subject: [PATCH 20/24] :recycle: move: exception handler --- .../{exception => handler}/GlobalExceptionHandler.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename src/main/java/com/server/global/error/{exception => handler}/GlobalExceptionHandler.java (83%) diff --git a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java b/src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java similarity index 83% rename from src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java rename to src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java index 0fa0ac7..7deaa71 100644 --- a/src/main/java/com/server/global/error/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java @@ -1,14 +1,13 @@ -package com.server.global.error.exception; +package com.server.global.error.handler; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import com.server.global.dto.ApiResponseDto; +import com.server.global.error.exception.AuthException; +import com.server.global.error.exception.BusinessException; -import lombok.extern.slf4j.Slf4j; - -@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { From 036b722ba29cec9bc7d8da5cd6598ff5ebe2a9e3 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:16:00 +0900 Subject: [PATCH 21/24] :memo: docs: service layer design --- README.md | 7 ++++--- docs/service.md | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 docs/service.md diff --git a/README.md b/README.md index ba943dd..2397d12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ## Docs -- [docker-compose](docs/docker-compose.md) -- [kubernetes](docs/kubernetes.md) -- [package-structure](docs/package-structure.md) +- [Package 구조](docs/package-structure.md) +- [Service layer 개발 방법](docs/service.md) +- [Docker Compose 사용 방법](docs/docker-compose.md) +- [Kubernetes 사용 방법](docs/kubernetes.md) diff --git a/docs/service.md b/docs/service.md new file mode 100644 index 0000000..126f175 --- /dev/null +++ b/docs/service.md @@ -0,0 +1,27 @@ +## Exception Handling + +> Service layer의 exception handling은 [`GlobalExceptionHandler`](src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java)를 통해 관리 + +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(AuthException.class) + public ResponseEntity> handleFriendException(AuthException e) { + return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage())); + } + + @ExceptionHandler(BusinessException.class) + public ResponseEntity> handleFriendException(BusinessException e) { + return ResponseEntity.status(e.getStatus()).body(ApiResponseDto.error(e.getStatus(), e.getMessage())); + } + +} +``` + +> [`ErrorCode`](src/main/java/com/server/global/error/code) 및 [`Exception`](src/main/java/com/server/global/error/exception) 정의 후 아래 예시와 같이 service layer에서 예외 생성 + +```java +throw new AuthException(AuthErrorCode.OAUTH_PROCESS_ERROR); +throw new BusinessException(FriendErrorCode.RECEIPT_ALREADY_EXISTS); +``` From 7aa9e75a04eb6d0eb5acddb8af626dfc02c3dc74 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:17:44 +0900 Subject: [PATCH 22/24] :memo: docs: relative path --- docs/service.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/service.md b/docs/service.md index 126f175..d8bdfb6 100644 --- a/docs/service.md +++ b/docs/service.md @@ -1,6 +1,6 @@ ## Exception Handling -> Service layer의 exception handling은 [`GlobalExceptionHandler`](src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java)를 통해 관리 +> Service layer의 exception handling은 [`GlobalExceptionHandler`](../src/main/java/com/server/global/error/handler/GlobalExceptionHandler.java)를 통해 관리 ```java @RestControllerAdvice @@ -19,7 +19,7 @@ public class GlobalExceptionHandler { } ``` -> [`ErrorCode`](src/main/java/com/server/global/error/code) 및 [`Exception`](src/main/java/com/server/global/error/exception) 정의 후 아래 예시와 같이 service layer에서 예외 생성 +> [`ErrorCode`](../src/main/java/com/server/global/error/code) 및 [`Exception`](../src/main/java/com/server/global/error/exception) 정의 후 아래 예시와 같이 service layer에서 예외 생성 ```java throw new AuthException(AuthErrorCode.OAUTH_PROCESS_ERROR); From 910fb958bec1d8ff365346e1b7b5ab2789d8746e Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:21:34 +0900 Subject: [PATCH 23/24] :memo: docs: controller design --- README.md | 1 + docs/controller.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docs/controller.md diff --git a/README.md b/README.md index 2397d12..19afed2 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,6 @@ - [Package 구조](docs/package-structure.md) - [Service layer 개발 방법](docs/service.md) +- [Controller 개발 방법](docs/controller.md) - [Docker Compose 사용 방법](docs/docker-compose.md) - [Kubernetes 사용 방법](docs/kubernetes.md) diff --git a/docs/controller.md b/docs/controller.md new file mode 100644 index 0000000..2696d9f --- /dev/null +++ b/docs/controller.md @@ -0,0 +1,28 @@ +## Controller 규약 + +```java +@ResponseStatus(HttpStatus.OK) // 정상적 응답에 대한 HTTP status 작성 +@GetMapping("/${URI}") // API 접근 URI 명시 +@Operation(summary = "${SUMMARY}", description = "${DESCRIPTION}") // Swagger 내 문서화를 위한 설명 작성 +// Controller의 모든 응답에 ApiResponseDto class 사용 +// Generic을 통해 응답의 data type 명시 +public ApiResponseDto methodName( + HttpServletRequest request, // 직접적인 요청 정보가 필요할 때 사용 + @RequestParam String param1, // 쿼리 스트링 또는 폼 데이터의 요청 파라미터 + @PathVariable Long param2, // URL 경로의 변수 매핑 + @RequestBody Dto param3, // 요청 본문(JSON 등)을 객체로 받음 + HttpServletResponse response // 직접적인 응답 설정이 필요할 때 사용 +) { + // Request header 사용 예시 + String authHeader = request.getHeader("Authorization"); + + // 결과를 위한 business logic 실행 + T data = domainService.businessLogic(); + + // Response header 추가 예시 + response.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); + + // 정상적인 응답 시 사용할 HTTP status 및 응답 data + return ApiResponseDto.success(HttpStatus.OK.value(), data); +} +``` From 03ae5d892223db78012cf457b760939692c0f410 Mon Sep 17 00:00:00 2001 From: Zerohertz Date: Thu, 31 Oct 2024 00:26:41 +0900 Subject: [PATCH 24/24] :recycle: style: friend controller --- .../friend/controller/FriendController.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/server/domain/friend/controller/FriendController.java b/src/main/java/com/server/domain/friend/controller/FriendController.java index baa12b4..7f2ef31 100644 --- a/src/main/java/com/server/domain/friend/controller/FriendController.java +++ b/src/main/java/com/server/domain/friend/controller/FriendController.java @@ -35,8 +35,8 @@ public class FriendController { @ResponseStatus(HttpStatus.CREATED) @PostMapping("/request") @Operation(summary = "친구 신청 생성", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 생성") - public ApiResponseDto createFriendRequest(@RequestParam String receiptUsername, - HttpServletRequest request) { + public ApiResponseDto createFriendRequest(HttpServletRequest request, + @RequestParam String receiptUsername) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.createFriendRequest(requestUsername, receiptUsername); return ApiResponseDto.success(HttpStatus.CREATED.value(), @@ -58,8 +58,8 @@ public ApiResponseDto> getFriendRequest( @ResponseStatus(HttpStatus.OK) @DeleteMapping("/request") @Operation(summary = "친구 신청 취소", description = "친구 신청할 사용자의 이름을 입력하여 친구 신청 삭제") - public ApiResponseDto deleteFriendRequest(@RequestParam String receiptUsername, - HttpServletRequest request) { + public ApiResponseDto deleteFriendRequest(HttpServletRequest request, + @RequestParam String receiptUsername) { String requestUsername = jwtService.extractUsernameFromToken(request).get(); friendService.deleteFriendRequest(requestUsername, receiptUsername); return ApiResponseDto.success(HttpStatus.OK.value(), String @@ -69,9 +69,8 @@ public ApiResponseDto deleteFriendRequest(@RequestParam String receiptUs @ResponseStatus(HttpStatus.OK) @GetMapping("/receipt") @Operation(summary = "친구 내역 조회", description = "사용자 기준 신청 받은 내역") - public ApiResponseDto> getFriendReceipt( - @RequestParam(required = false) FriendState state, - HttpServletRequest request) { + public ApiResponseDto> getFriendReceipt(HttpServletRequest request, + @RequestParam(required = false) FriendState state) { String username = jwtService.extractUsernameFromToken(request).get(); List getUserOutDtos = friendService.getReceiptUser(username, state); return ApiResponseDto.success(HttpStatus.OK.value(), getUserOutDtos); @@ -80,8 +79,8 @@ public ApiResponseDto> getFriendReceipt( @ResponseStatus(HttpStatus.CREATED) @PutMapping("/receipt") @Operation(summary = "친구 신청 승인", description = "친구 신청을 승인할 사용자의 이름을 입력하여 친구 신청 승인") - public ApiResponseDto approveFriendRequest(@RequestParam String requestUsername, - HttpServletRequest request) { + public ApiResponseDto approveFriendRequest(HttpServletRequest request, + @RequestParam String requestUsername) { String receiptUsername = jwtService.extractUsernameFromToken(request).get(); friendService.acceptFriendRequest(requestUsername, receiptUsername); return ApiResponseDto.success(HttpStatus.CREATED.value(), String.format(