diff --git a/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java b/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java new file mode 100644 index 000000000..82a7cf024 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java @@ -0,0 +1,16 @@ +package org.websoso.WSSServer.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.websoso.WSSServer.domain.User; + +@Service +@RequiredArgsConstructor +public class AuthorizationService { + + private final ResourceAuthorizationHandler resourceAuthorizationHandler; + + public boolean validate(Long resourceId, User user, Class resourceType) { + return resourceAuthorizationHandler.authorizeResourceAccess(resourceId, user, resourceType); + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java b/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java new file mode 100644 index 000000000..27e9bf22e --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java @@ -0,0 +1,43 @@ +package org.websoso.WSSServer.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.service.UserService; + +@Component +@RequiredArgsConstructor +public class CustomUserArgumentResolver implements HandlerMethodArgumentResolver { + + private final UserService userService; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(User.class) && + parameter.hasParameterAnnotation(AuthenticationPrincipal.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { + return null; + } + + Long userId = (Long) authentication.getPrincipal(); + return userService.getUserOrException(userId); + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java b/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java new file mode 100644 index 000000000..4fc7c1bbb --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java @@ -0,0 +1,33 @@ +package org.websoso.WSSServer.auth; + +import static org.websoso.WSSServer.exception.error.CustomAuthorizationError.UNSUPPORTED_RESOURCE_TYPE; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.auth.validator.ResourceAuthorizationValidator; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.exception.exception.CustomAuthorizationException; + +@Component +public class ResourceAuthorizationHandler { + + private final Map, ResourceAuthorizationValidator> validatorMap = new HashMap<>(); + + @Autowired + public ResourceAuthorizationHandler(List validators) { + for (ResourceAuthorizationValidator validator : validators) { + validatorMap.put(validator.getResourceType(), validator); + } + } + + public boolean authorizeResourceAccess(Long resourceId, User user, Class resourceType) { + return Optional.ofNullable(validatorMap.get(resourceType)) + .orElseThrow(() -> new CustomAuthorizationException( + UNSUPPORTED_RESOURCE_TYPE, "Unsupported resource type for authorization")) + .hasPermission(resourceId, user); + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java new file mode 100644 index 000000000..0daba4aac --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java @@ -0,0 +1,44 @@ +package org.websoso.WSSServer.auth.validator; + +import static org.websoso.WSSServer.exception.error.CustomBlockError.BLOCK_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomBlockError.INVALID_AUTHORIZED_BLOCK; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.Block; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.exception.exception.CustomBlockException; +import org.websoso.WSSServer.repository.BlockRepository; + +@Component +@RequiredArgsConstructor +public class BlockAuthorizationValidator implements ResourceAuthorizationValidator { + + private final BlockRepository blockRepository; + + @Override + public boolean hasPermission(Long blockId, User user) { + Block block = getBlockOrException(blockId); + + if (!isBlockOwner(block, user)) { + throw new CustomBlockException(INVALID_AUTHORIZED_BLOCK, + "block with the given blockId is not from user with the given userId"); + } + return true; + } + + private Block getBlockOrException(Long blockId) { + return blockRepository.findById(blockId) + .orElseThrow(() -> new CustomBlockException(BLOCK_NOT_FOUND, + "block with the given blockId was not found")); + } + + private boolean isBlockOwner(Block block, User user) { + return block.getBlockingId().equals(user.getUserId()); + } + + @Override + public Class getResourceType() { + return Block.class; + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java new file mode 100644 index 000000000..612167f11 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java @@ -0,0 +1,45 @@ +package org.websoso.WSSServer.auth.validator; + +import static org.websoso.WSSServer.exception.error.CustomFeedError.BLOCKED_USER_ACCESS; +import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomFeedError.HIDDEN_FEED_ACCESS; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.repository.FeedRepository; +import org.websoso.WSSServer.service.BlockService; + +@Component +@RequiredArgsConstructor +public class FeedAccessValidator { + + private final FeedRepository feedRepository; + private final BlockService blockService; + + public boolean canAccess(Long feedId, User user) { + Feed feed = getFeedOrException(feedId); + + if (feed.getUser().equals(user)) { + return true; + } + + if (feed.getIsHidden()) { + throw new CustomFeedException(HIDDEN_FEED_ACCESS, "Cannot access hidden feed."); + } + + if (blockService.isBlocked(user.getUserId(), feed.getWriterId())) { + throw new CustomFeedException(BLOCKED_USER_ACCESS, + "cannot access this feed because either you or the feed author has blocked the other."); + } + + return true; + } + + private Feed getFeedOrException(Long feedId) { + return feedRepository.findById(feedId) + .orElseThrow(() -> new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java new file mode 100644 index 000000000..593576414 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java @@ -0,0 +1,40 @@ +package org.websoso.WSSServer.auth.validator; + +import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomUserError.INVALID_AUTHORIZED; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.exception.exception.CustomUserException; +import org.websoso.WSSServer.repository.FeedRepository; + +@Component +@RequiredArgsConstructor +public class FeedAuthorizationValidator implements ResourceAuthorizationValidator { + + private final FeedRepository feedRepository; + + @Override + public boolean hasPermission(Long feedId, User user) { + Feed feed = getFeedOrException(feedId); + + if (!feed.isMine(user.getUserId())) { + throw new CustomUserException(INVALID_AUTHORIZED, + "User with ID " + user.getUserId() + " is not the owner of feed " + feed.getFeedId()); + } + return true; + } + + private Feed getFeedOrException(Long feedId) { + return feedRepository.findById(feedId) + .orElseThrow(() -> new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + } + + @Override + public Class getResourceType() { + return Feed.class; + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java new file mode 100644 index 000000000..e21383e68 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java @@ -0,0 +1,49 @@ +package org.websoso.WSSServer.auth.validator; + +import static org.websoso.WSSServer.domain.common.Role.ADMIN; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_ADMIN_ONLY; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.Notification; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.exception.exception.CustomNotificationException; +import org.websoso.WSSServer.repository.NotificationRepository; + +@Component +@RequiredArgsConstructor +public class NotificationAuthorizationValidator implements ResourceAuthorizationValidator { + + private final NotificationRepository notificationRepository; + + @Override + public boolean hasPermission(Long resourceId, User user) { + if (resourceId == null) { + return isAdmin(user); + } + + // 수정이나 삭제에서 리소스 존재 여부 검증을 위함 + Notification notification = getNotificationOrException(resourceId); + return isAdmin(user); + } + + private Notification getNotificationOrException(Long notificationId) { + return notificationRepository.findById(notificationId) + .orElseThrow(() -> new CustomNotificationException(NOTIFICATION_NOT_FOUND, + "notification with the given id is not found")); + } + + private boolean isAdmin(User user) { + if (user.getRole() != ADMIN) { + throw new CustomNotificationException(NOTIFICATION_ADMIN_ONLY, + "User who tried to create, modify, or delete the notice is not an ADMIN."); + } + return true; + } + + @Override + public Class getResourceType() { + return Notification.class; + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java new file mode 100644 index 000000000..0882b01a9 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java @@ -0,0 +1,33 @@ +package org.websoso.WSSServer.auth.validator; + +import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.exception.exception.CustomNovelException; +import org.websoso.WSSServer.repository.NovelRepository; + +@Component +@RequiredArgsConstructor +public class NovelAuthorizationValidator implements ResourceAuthorizationValidator { + + private final NovelRepository novelRepository; + + @Override + public boolean hasPermission(Long resourceId, User user) { + Novel novel = getNovelOrException(resourceId); + return true; + } + + private Novel getNovelOrException(Long novelId) { + return novelRepository.findById(novelId) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found")); + } + + @Override + public Class getResourceType() { + return Novel.class; + } +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java new file mode 100644 index 000000000..acda77266 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java @@ -0,0 +1,10 @@ +package org.websoso.WSSServer.auth.validator; + +import org.websoso.WSSServer.domain.User; + +public interface ResourceAuthorizationValidator { + + boolean hasPermission(Long resourceId, User user); + + Class getResourceType(); +} diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java new file mode 100644 index 000000000..46145cc8b --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java @@ -0,0 +1,45 @@ +package org.websoso.WSSServer.auth.validator; + +import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.USER_NOVEL_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.exception.exception.CustomNovelException; +import org.websoso.WSSServer.exception.exception.CustomUserNovelException; +import org.websoso.WSSServer.repository.NovelRepository; +import org.websoso.WSSServer.repository.UserNovelRepository; + +@Component +@RequiredArgsConstructor +public class UserNovelAuthorizationValidator implements ResourceAuthorizationValidator { + + private final NovelRepository novelRepository; + private final UserNovelRepository userNovelRepository; + + @Override + public boolean hasPermission(Long resourceId, User user) { + Novel novel = getNovelOrException(resourceId); + UserNovel userNovel = getUserNovelOrException(user, novel); + return true; + } + + private Novel getNovelOrException(Long novelId) { + return novelRepository.findById(novelId) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found")); + } + + private UserNovel getUserNovelOrException(User user, Novel novel) { + return userNovelRepository.findByNovelAndUser(novel, user) + .orElseThrow(() -> new CustomUserNovelException(USER_NOVEL_NOT_FOUND, + "user novel with the given user and novel is not found")); + } + + @Override + public Class getResourceType() { + return UserNovel.class; + } +} diff --git a/src/main/java/org/websoso/WSSServer/config/SecurityConfig.java b/src/main/java/org/websoso/WSSServer/config/SecurityConfig.java index d37776141..cb08453e7 100644 --- a/src/main/java/org/websoso/WSSServer/config/SecurityConfig.java +++ b/src/main/java/org/websoso/WSSServer/config/SecurityConfig.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -18,6 +19,7 @@ @Configuration @RequiredArgsConstructor @EnableWebSecurity +@EnableMethodSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; diff --git a/src/main/java/org/websoso/WSSServer/config/WebConfig.java b/src/main/java/org/websoso/WSSServer/config/WebConfig.java new file mode 100644 index 000000000..5045fbc0a --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/config/WebConfig.java @@ -0,0 +1,20 @@ +package org.websoso.WSSServer.config; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.websoso.WSSServer.auth.CustomUserArgumentResolver; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final CustomUserArgumentResolver customUserArgumentResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(customUserArgumentResolver); + } +} diff --git a/src/main/java/org/websoso/WSSServer/config/jwt/CustomAuthenticationToken.java b/src/main/java/org/websoso/WSSServer/config/jwt/CustomAuthenticationToken.java new file mode 100644 index 000000000..f4113deda --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/config/jwt/CustomAuthenticationToken.java @@ -0,0 +1,14 @@ +package org.websoso.WSSServer.config.jwt; + +import java.util.Collection; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken { + + public CustomAuthenticationToken(Object principal, + Object credentials, + Collection authorities) { + super(principal, credentials, authorities); + } +} diff --git a/src/main/java/org/websoso/WSSServer/config/jwt/JWTUtil.java b/src/main/java/org/websoso/WSSServer/config/jwt/JWTUtil.java index ffa3c1d9c..095083d11 100644 --- a/src/main/java/org/websoso/WSSServer/config/jwt/JWTUtil.java +++ b/src/main/java/org/websoso/WSSServer/config/jwt/JWTUtil.java @@ -7,6 +7,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.SignatureException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -29,6 +30,8 @@ public JwtValidationType validateJWT(String token) { return JwtValidationType.VALID_ACCESS; } return JwtValidationType.VALID_REFRESH; + } catch (SignatureException ex) { + return JwtValidationType.INVALID_SIGNATURE; } catch (MalformedJwtException ex) { return JwtValidationType.INVALID_TOKEN; } catch (ExpiredJwtException ex) { diff --git a/src/main/java/org/websoso/WSSServer/config/jwt/JwtAuthenticationFilter.java b/src/main/java/org/websoso/WSSServer/config/jwt/JwtAuthenticationFilter.java index 5edf3d16b..5fa8dd7d6 100644 --- a/src/main/java/org/websoso/WSSServer/config/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/org/websoso/WSSServer/config/jwt/JwtAuthenticationFilter.java @@ -1,6 +1,8 @@ package org.websoso.WSSServer.config.jwt; import static org.websoso.WSSServer.config.jwt.JwtValidationType.EXPIRED_ACCESS; +import static org.websoso.WSSServer.config.jwt.JwtValidationType.INVALID_SIGNATURE; +import static org.websoso.WSSServer.config.jwt.JwtValidationType.INVALID_TOKEN; import static org.websoso.WSSServer.config.jwt.JwtValidationType.VALID_ACCESS; import jakarta.servlet.FilterChain; @@ -12,6 +14,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; @@ -30,24 +34,29 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - try { - final String token = getJwtFromRequest(request); + final String token = getJwtFromRequest(request); + + if (StringUtils.hasText(token)) { final JwtValidationType validationResult = jwtUtil.validateJWT(token); if (validationResult == VALID_ACCESS) { Long userId = jwtUtil.getUserIdFromJwt(token); - UserAuthentication authentication = new UserAuthentication(userId.toString(), null, null); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(userId, null, null); + customAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(customAuthenticationToken); } else if (validationResult == EXPIRED_ACCESS) { handleExpiredAccessToken(request, response); return; + } else if (validationResult == INVALID_TOKEN || validationResult == INVALID_SIGNATURE) { + handleInvalidToken(response); + return; } - } catch (Exception exception) { - try { - throw new Exception(); - } catch (Exception e) { - throw new RuntimeException(e); - } + } else { + SecurityContextHolder.getContext().setAuthentication( + new AnonymousAuthenticationToken( + "anonymous", + "anonymousUser", + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")) + ); } filterChain.doFilter(request, response); } @@ -67,4 +76,11 @@ private void handleExpiredAccessToken(HttpServletRequest request, response.getWriter() .write("{\"code\": \"AUTH-000\", \"message\": \"Access Token Expired. Use Refresh Token to reissue.\"}"); } + + private void handleInvalidToken(HttpServletResponse response) throws IOException { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter() + .write("{\"code\": \"AUTH-001\", \"message\": \"Invalid token.\"}"); + } } diff --git a/src/main/java/org/websoso/WSSServer/config/jwt/JwtValidationType.java b/src/main/java/org/websoso/WSSServer/config/jwt/JwtValidationType.java index ffcc462d6..bb06935fa 100644 --- a/src/main/java/org/websoso/WSSServer/config/jwt/JwtValidationType.java +++ b/src/main/java/org/websoso/WSSServer/config/jwt/JwtValidationType.java @@ -4,6 +4,7 @@ public enum JwtValidationType { VALID_ACCESS, VALID_REFRESH, INVALID_TOKEN, + INVALID_SIGNATURE, EXPIRED_ACCESS, EXPIRED_REFRESH, UNSUPPORTED_TOKEN, diff --git a/src/main/java/org/websoso/WSSServer/config/jwt/UserAuthentication.java b/src/main/java/org/websoso/WSSServer/config/jwt/UserAuthentication.java deleted file mode 100644 index 79b458893..000000000 --- a/src/main/java/org/websoso/WSSServer/config/jwt/UserAuthentication.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.websoso.WSSServer.config.jwt; - -import java.util.Collection; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; - -public class UserAuthentication extends UsernamePasswordAuthenticationToken { - - // 사용자 인증 객체 생성 - public UserAuthentication(Object principal, Object credentials, - Collection authorities) { - super(principal, credentials, authorities); - } -} \ No newline at end of file diff --git a/src/main/java/org/websoso/WSSServer/controller/AuthController.java b/src/main/java/org/websoso/WSSServer/controller/AuthController.java index c63833b61..5685e723b 100644 --- a/src/main/java/org/websoso/WSSServer/controller/AuthController.java +++ b/src/main/java/org/websoso/WSSServer/controller/AuthController.java @@ -4,9 +4,10 @@ import static org.springframework.http.HttpStatus.OK; import jakarta.validation.Valid; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -34,10 +35,9 @@ public class AuthController { @PostMapping("/reissue") public ResponseEntity reissue(@RequestBody ReissueRequest reissueRequest) { - String refreshToken = reissueRequest.refreshToken(); return ResponseEntity .status(OK) - .body(authService.reissue(refreshToken)); + .body(authService.reissue(reissueRequest.refreshToken())); } @PostMapping("/auth/login/kakao") @@ -55,21 +55,19 @@ public ResponseEntity loginByApple(@Valid @RequestBody AppleLoginR } @PostMapping("/auth/logout") - public ResponseEntity logout(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity logout(@AuthenticationPrincipal User user, @Valid @RequestBody LogoutRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - String refreshToken = request.refreshToken(); - String deviceIdentifier = request.deviceIdentifier(); - userService.logout(user, refreshToken, deviceIdentifier); + userService.logout(user, request); return ResponseEntity .status(NO_CONTENT) .build(); } @PostMapping("/auth/withdraw") - public ResponseEntity withdrawUser(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity withdrawUser(@AuthenticationPrincipal User user, @Valid @RequestBody WithdrawalRequest withdrawalRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); userService.withdrawUser(user, withdrawalRequest); return ResponseEntity .status(NO_CONTENT) diff --git a/src/main/java/org/websoso/WSSServer/controller/AvatarController.java b/src/main/java/org/websoso/WSSServer/controller/AvatarController.java index 9154c11af..20e224b02 100644 --- a/src/main/java/org/websoso/WSSServer/controller/AvatarController.java +++ b/src/main/java/org/websoso/WSSServer/controller/AvatarController.java @@ -2,16 +2,16 @@ import static org.springframework.http.HttpStatus.OK; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.websoso.WSSServer.domain.User; import org.websoso.WSSServer.dto.avatar.AvatarsGetResponse; import org.websoso.WSSServer.service.AvatarService; -import org.websoso.WSSServer.service.UserService; @RequestMapping("/avatars") @RestController @@ -19,12 +19,10 @@ public class AvatarController { private final AvatarService avatarService; - private final UserService userService; - @GetMapping - public ResponseEntity getAvatarList(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @PreAuthorize("isAuthenticated()") + public ResponseEntity getAvatarList(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(avatarService.getAvatarList(user)); diff --git a/src/main/java/org/websoso/WSSServer/controller/BlockController.java b/src/main/java/org/websoso/WSSServer/controller/BlockController.java index 602a354c8..dc5876784 100644 --- a/src/main/java/org/websoso/WSSServer/controller/BlockController.java +++ b/src/main/java/org/websoso/WSSServer/controller/BlockController.java @@ -4,9 +4,10 @@ import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.OK; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -17,7 +18,6 @@ import org.websoso.WSSServer.domain.User; import org.websoso.WSSServer.dto.block.BlocksGetResponse; import org.websoso.WSSServer.service.BlockService; -import org.websoso.WSSServer.service.UserService; import org.websoso.WSSServer.validation.BlockIdConstraint; import org.websoso.WSSServer.validation.UserIdConstraint; @@ -26,13 +26,12 @@ @RequiredArgsConstructor public class BlockController { - private final UserService userService; private final BlockService blockService; @PostMapping - public ResponseEntity block(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity block(@AuthenticationPrincipal User blocker, @RequestParam("userId") @UserIdConstraint Long blockedId) { - User blocker = userService.getUserOrException(Long.valueOf(principal.getName())); blockService.block(blocker, blockedId); return ResponseEntity .status(CREATED) @@ -40,18 +39,18 @@ public ResponseEntity block(Principal principal, } @GetMapping - public ResponseEntity getBlockList(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @PreAuthorize("isAuthenticated()") + public ResponseEntity getBlockList(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(blockService.getBlockList(user)); } @DeleteMapping("/{blockId}") - public ResponseEntity deleteBlock(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#blockId, #user, T(org.websoso.WSSServer.domain.Block))") + public ResponseEntity deleteBlock(@AuthenticationPrincipal User user, @PathVariable("blockId") @BlockIdConstraint Long blockId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - blockService.deleteBlock(user, blockId); + blockService.deleteBlock(blockId); return ResponseEntity .status(NO_CONTENT) .build(); diff --git a/src/main/java/org/websoso/WSSServer/controller/FeedController.java b/src/main/java/org/websoso/WSSServer/controller/FeedController.java index 5abffe187..84d2c7b13 100644 --- a/src/main/java/org/websoso/WSSServer/controller/FeedController.java +++ b/src/main/java/org/websoso/WSSServer/controller/FeedController.java @@ -7,9 +7,10 @@ import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER; import jakarta.validation.Valid; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -31,7 +32,6 @@ import org.websoso.WSSServer.dto.popularFeed.PopularFeedsGetResponse; import org.websoso.WSSServer.service.FeedService; import org.websoso.WSSServer.service.PopularFeedService; -import org.websoso.WSSServer.service.UserService; @RequestMapping("/feeds") @RestController @@ -39,195 +39,173 @@ public class FeedController { private final FeedService feedService; - private final UserService userService; private final PopularFeedService popularFeedService; @PostMapping - public ResponseEntity createFeed(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity createFeed(@AuthenticationPrincipal User user, @Valid @RequestBody FeedCreateRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.createFeed(user, request); - return ResponseEntity .status(CREATED) .build(); } + @GetMapping("/{feedId}") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity getFeed(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + return ResponseEntity + .status(OK) + .body(feedService.getFeedById(user, feedId)); + } + + @GetMapping + public ResponseEntity getFeeds(@AuthenticationPrincipal User user, + @RequestParam(value = "category", required = false) String category, + @RequestParam("lastFeedId") Long lastFeedId, + @RequestParam("size") int size) { + return ResponseEntity + .status(OK) + .body(feedService.getFeeds(user, category, lastFeedId, size)); + } + @PutMapping("/{feedId}") - public ResponseEntity updateFeed(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.domain.Feed))") + public ResponseEntity updateFeed(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, @Valid @RequestBody FeedUpdateRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.updateFeed(user, feedId, request); - + feedService.updateFeed(feedId, request); return ResponseEntity .status(NO_CONTENT) .build(); } @DeleteMapping("/{feedId}") - public ResponseEntity deleteFeed(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.domain.Feed))") + public ResponseEntity deleteFeed(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.deleteFeed(user, feedId); - + feedService.deleteFeed(feedId); return ResponseEntity .status(NO_CONTENT) .build(); } @PostMapping("/{feedId}/likes") - public ResponseEntity likeFeed(Principal principal, + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity likeFeed(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.likeFeed(user, feedId); - return ResponseEntity .status(NO_CONTENT) .build(); } @DeleteMapping("/{feedId}/likes") - public ResponseEntity unLikeFeed(Principal principal, + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity unLikeFeed(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.unLikeFeed(user, feedId); - return ResponseEntity .status(NO_CONTENT) .build(); } - @GetMapping("/{feedId}") - public ResponseEntity getFeed(Principal principal, - @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - - return ResponseEntity - .status(OK) - .body(feedService.getFeedById(user, feedId)); - } - @GetMapping("/popular") - public ResponseEntity getPopularFeeds(Principal principal) { - User user = principal == null ? - null : - userService.getUserOrException(Long.valueOf(principal.getName())); + public ResponseEntity getPopularFeeds(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(popularFeedService.getPopularFeeds(user)); } - @GetMapping - public ResponseEntity getFeeds(Principal principal, - @RequestParam(value = "category", required = false) String category, - @RequestParam("lastFeedId") Long lastFeedId, - @RequestParam("size") int size) { - User user = principal == null ? null : userService.getUserOrException(Long.valueOf(principal.getName())); - - return ResponseEntity - .status(OK) - .body(feedService.getFeeds(user, category, lastFeedId, size)); - } - @GetMapping("/interest") - public ResponseEntity getInterestFeeds(Principal principal) { - User user = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); + @PreAuthorize("isAuthenticated()") + public ResponseEntity getInterestFeeds(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(feedService.getInterestFeeds(user)); } @PostMapping("/{feedId}/comments") - public ResponseEntity createComment(Principal principal, + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity createComment(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, @Valid @RequestBody CommentCreateRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.createComment(user, feedId, request); - return ResponseEntity .status(NO_CONTENT) .build(); } + @GetMapping("/{feedId}/comments") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity getComments(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + return ResponseEntity + .status(OK) + .body(feedService.getComments(user, feedId)); + } + @PutMapping("/{feedId}/comments/{commentId}") - public ResponseEntity updateComment(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.domain.Feed))") + public ResponseEntity updateComment(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId, @Valid @RequestBody CommentUpdateRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.updateComment(user, feedId, commentId, request); - return ResponseEntity .status(NO_CONTENT) .build(); } @DeleteMapping("/{feedId}/comments/{commentId}") - public ResponseEntity deleteComment(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.domain.Feed))") + public ResponseEntity deleteComment(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.deleteComment(user, feedId, commentId); - return ResponseEntity .status(NO_CONTENT) .build(); } - @GetMapping("/{feedId}/comments") - public ResponseEntity getComments(Principal principal, - @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - - return ResponseEntity - .status(OK) - .body(feedService.getComments(user, feedId)); - } - @PostMapping("/{feedId}/spoiler") - public ResponseEntity reportFeedSpoiler(Principal principal, + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity reportFeedSpoiler(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.reportFeed(user, feedId, SPOILER); - return ResponseEntity .status(CREATED) .build(); } @PostMapping("/{feedId}/impertinence") - public ResponseEntity reportedFeedImpertinence(Principal principal, + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity reportedFeedImpertinence(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.reportFeed(user, feedId, IMPERTINENCE); - return ResponseEntity .status(CREATED) .build(); } @PostMapping("/{feedId}/comments/{commentId}/spoiler") - public ResponseEntity reportCommentSpoiler(Principal principal, + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity reportCommentSpoiler(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.reportComment(user, feedId, commentId, SPOILER); - return ResponseEntity .status(CREATED) .build(); } @PostMapping("/{feedId}/comments/{commentId}/impertinence") - public ResponseEntity reportCommentImpertinence(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity reportCommentImpertinence(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); feedService.reportComment(user, feedId, commentId, IMPERTINENCE); - return ResponseEntity .status(CREATED) .build(); diff --git a/src/main/java/org/websoso/WSSServer/controller/NoticeController.java b/src/main/java/org/websoso/WSSServer/controller/NoticeController.java deleted file mode 100644 index 411f22b36..000000000 --- a/src/main/java/org/websoso/WSSServer/controller/NoticeController.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.websoso.WSSServer.controller; - -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.NO_CONTENT; - -import jakarta.validation.Valid; -import java.security.Principal; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.dto.notice.NoticeEditRequest; -import org.websoso.WSSServer.dto.notice.NoticePostRequest; -import org.websoso.WSSServer.service.NoticeService; -import org.websoso.WSSServer.service.UserService; - -@RestController -@RequestMapping("/notices") -@RequiredArgsConstructor -public class NoticeController { - - private final NoticeService noticeService; - private final UserService userService; - - @PostMapping - public ResponseEntity createNotice(Principal principal, - @Valid @RequestBody NoticePostRequest noticePostRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - noticeService.createNotice(user, noticePostRequest); - return ResponseEntity - .status(CREATED) - .build(); - } - - @PutMapping("/{noticeId}") - public ResponseEntity editNotice(Principal principal, - @PathVariable("noticeId") Long noticeId, - @Valid @RequestBody NoticeEditRequest noticeEditRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - noticeService.editNotice(user, noticeId, noticeEditRequest); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @DeleteMapping("/{noticeId}") - public ResponseEntity deleteNotice(Principal principal, - @PathVariable("noticeId") Long noticeId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - noticeService.deleteNotice(user, noticeId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } -} diff --git a/src/main/java/org/websoso/WSSServer/controller/NotificationController.java b/src/main/java/org/websoso/WSSServer/controller/NotificationController.java index b35c78ceb..c2f31a61b 100644 --- a/src/main/java/org/websoso/WSSServer/controller/NotificationController.java +++ b/src/main/java/org/websoso/WSSServer/controller/NotificationController.java @@ -4,9 +4,10 @@ import static org.springframework.http.HttpStatus.OK; import jakarta.validation.Valid; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -20,7 +21,6 @@ import org.websoso.WSSServer.dto.notification.NotificationsGetResponse; import org.websoso.WSSServer.dto.notification.NotificationsReadStatusGetResponse; import org.websoso.WSSServer.service.NotificationService; -import org.websoso.WSSServer.service.UserService; @RestController @RequestMapping("/notifications") @@ -28,53 +28,52 @@ public class NotificationController { private final NotificationService notificationService; - private final UserService userService; + + @PostMapping + @PreAuthorize("isAuthenticated() and @authorizationService.validate(null, #user, T(org.websoso.WSSServer.domain.Notification))") + public ResponseEntity createNoticeNotification(@AuthenticationPrincipal User user, + @Valid @RequestBody NotificationCreateRequest notificationCreateRequest) { + notificationService.createNoticeNotification(user, notificationCreateRequest); + return ResponseEntity + .status(CREATED) + .build(); + } @GetMapping - public ResponseEntity getNotifications(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity getNotifications(@AuthenticationPrincipal User user, @RequestParam("lastNotificationId") Long lastNotificationId, @RequestParam("size") int size) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); return ResponseEntity .status(OK) .body(notificationService.getNotifications(lastNotificationId, size, user)); } - @GetMapping("/unread") - public ResponseEntity checkNotificationsReadStatus(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @GetMapping("/{notificationId}") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getNotification(@AuthenticationPrincipal User user, + @PathVariable("notificationId") Long notificationId) { return ResponseEntity .status(OK) - .body(notificationService.checkNotificationsReadStatus(user)); + .body(notificationService.getNotification(user, notificationId)); } - @GetMapping("/{notificationId}") - public ResponseEntity getNotification(Principal principal, - @PathVariable("notificationId") Long notificationId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - + @GetMapping("/unread") + @PreAuthorize("isAuthenticated()") + public ResponseEntity checkNotificationsReadStatus( + @AuthenticationPrincipal User user) { return ResponseEntity .status(OK) - .body(notificationService.getNotification(user, notificationId)); + .body(notificationService.checkNotificationsReadStatus(user)); } @PostMapping("/{notificationId}/read") - public ResponseEntity createNotificationAsRead(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity createNotificationAsRead(@AuthenticationPrincipal User user, @PathVariable("notificationId") Long notificationId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); notificationService.createNotificationAsRead(user, notificationId); return ResponseEntity .status(CREATED) .build(); } - - @PostMapping - public ResponseEntity createNoticeNotification(Principal principal, - @Valid @RequestBody NotificationCreateRequest notificationCreateRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - notificationService.createNoticeNotification(user, notificationCreateRequest); - return ResponseEntity - .status(CREATED) - .build(); - } } diff --git a/src/main/java/org/websoso/WSSServer/controller/NovelController.java b/src/main/java/org/websoso/WSSServer/controller/NovelController.java index 7e078ac41..d341ad310 100644 --- a/src/main/java/org/websoso/WSSServer/controller/NovelController.java +++ b/src/main/java/org/websoso/WSSServer/controller/NovelController.java @@ -3,10 +3,11 @@ import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.OK; -import java.security.Principal; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -24,7 +25,6 @@ import org.websoso.WSSServer.dto.userNovel.TasteNovelsGetResponse; import org.websoso.WSSServer.service.FeedService; import org.websoso.WSSServer.service.NovelService; -import org.websoso.WSSServer.service.UserService; @RestController @RequestMapping("/novels") @@ -32,16 +32,50 @@ public class NovelController { private final NovelService novelService; - private final UserService userService; private final FeedService feedService; + @GetMapping + public ResponseEntity searchNovels(@RequestParam(required = false) String query, + @RequestParam int page, + @RequestParam int size) { + return ResponseEntity + .status(OK) + .body(novelService.searchNovels(query, page, size)); + } + + @GetMapping("/filtered") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getFilteredNovels( + @RequestParam(required = false) List genres, + @RequestParam(required = false) Boolean isCompleted, + @RequestParam(required = false) Float novelRating, + @RequestParam(required = false) List keywordIds, + @RequestParam int page, + @RequestParam int size) { + return ResponseEntity + .status(OK) + .body(novelService.getFilteredNovels(genres, isCompleted, novelRating, keywordIds, page, size)); + } + + @GetMapping("/popular") + public ResponseEntity getTodayPopularNovels(@AuthenticationPrincipal User user) { + //TODO 차단 관계에 있는 유저의 피드글 처리 + return ResponseEntity + .status(OK) + .body(novelService.getTodayPopularNovels()); + } + + @GetMapping("/taste") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getTasteNovels(@AuthenticationPrincipal User user) { + return ResponseEntity + .status(OK) + .body(novelService.getTasteNovels(user)); + } + @GetMapping("/{novelId}") - public ResponseEntity getNovelInfoBasic(Principal principal, + public ResponseEntity getNovelInfoBasic(@AuthenticationPrincipal User user, @PathVariable Long novelId) { - User user = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); - return ResponseEntity .status(OK) .body(novelService.getNovelInfoBasic(user, novelId)); @@ -55,76 +89,32 @@ public ResponseEntity getNovelInfoInfoTab(@PathVariable } @GetMapping("/{novelId}/feeds") - public ResponseEntity getFeedsByNovel(Principal principal, + public ResponseEntity getFeedsByNovel(@AuthenticationPrincipal User user, @PathVariable Long novelId, @RequestParam("lastFeedId") Long lastFeedId, @RequestParam("size") int size) { - User user = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); - return ResponseEntity .status(OK) .body(feedService.getFeedsByNovel(user, novelId, lastFeedId, size)); } - @GetMapping - public ResponseEntity searchNovels(@RequestParam(required = false) String query, - @RequestParam int page, - @RequestParam int size) { - return ResponseEntity - .status(OK) - .body(novelService.searchNovels(query, page, size)); - } - - @GetMapping("/filtered") - public ResponseEntity getFilteredNovels( - @RequestParam(required = false) List genres, - @RequestParam(required = false) Boolean isCompleted, - @RequestParam(required = false) Float novelRating, - @RequestParam(required = false) List keywordIds, - @RequestParam int page, - @RequestParam int size) { - return ResponseEntity - .status(OK) - .body(novelService.getFilteredNovels(genres, isCompleted, novelRating, keywordIds, page, size)); - } - @PostMapping("/{novelId}/is-interest") - public ResponseEntity registerAsInterest(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.Novel))") + public ResponseEntity registerAsInterest(@AuthenticationPrincipal User user, @PathVariable("novelId") Long novelId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); novelService.registerAsInterest(user, novelId); - return ResponseEntity .status(NO_CONTENT) .build(); } @DeleteMapping("/{novelId}/is-interest") - public ResponseEntity unregisterAsInterest(Principal principal, + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.UserNovel))") + public ResponseEntity unregisterAsInterest(@AuthenticationPrincipal User user, @PathVariable("novelId") Long novelId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); novelService.unregisterAsInterest(user, novelId); - return ResponseEntity .status(NO_CONTENT) .build(); } - - @GetMapping("/popular") - public ResponseEntity getTodayPopularNovels(Principal principal) { - //TODO 차단 관계에 있는 유저의 피드글 처리 - return ResponseEntity - .status(OK) - .body(novelService.getTodayPopularNovels()); - } - - @GetMapping("/taste") - public ResponseEntity getTasteNovels(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - return ResponseEntity - .status(OK) - .body(novelService.getTasteNovels(user)); - } } diff --git a/src/main/java/org/websoso/WSSServer/controller/UserController.java b/src/main/java/org/websoso/WSSServer/controller/UserController.java index 103a72cba..d10e85719 100644 --- a/src/main/java/org/websoso/WSSServer/controller/UserController.java +++ b/src/main/java/org/websoso/WSSServer/controller/UserController.java @@ -5,9 +5,10 @@ import static org.springframework.http.HttpStatus.OK; import jakarta.validation.Valid; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -55,106 +56,101 @@ public class UserController { private final UserNovelService userNovelService; private final FeedService feedService; - @GetMapping("/nickname/check") - public ResponseEntity checkNicknameAvailability(Principal principal, - @RequestParam("nickname") - @NicknameConstraint String nickname) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @PostMapping("/login") + public ResponseEntity login(@RequestBody String userId) { + LoginResponse response = userService.login(Long.valueOf(userId)); return ResponseEntity .status(OK) - .body(userService.isNicknameAvailable(user, nickname)); + .body(response); } - @GetMapping("/info") - public ResponseEntity getUserInfo(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - return ResponseEntity - .status(OK) - .body(userService.getUserInfo(user)); + @PostMapping("/fcm-token") + @PreAuthorize("isAuthenticated()") + public ResponseEntity registerFCMToken(@AuthenticationPrincipal User user, + @Valid @RequestBody FCMTokenRequest fcmTokenRequest) { + return userService.registerFCMToken(user, fcmTokenRequest) + ? ResponseEntity.status(CREATED).build() + : ResponseEntity.status(NO_CONTENT).build(); } - @GetMapping("/profile-status") - public ResponseEntity getProfileStatus(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @GetMapping("/nickname/check") + @PreAuthorize("isAuthenticated()") + public ResponseEntity checkNicknameAvailability(@AuthenticationPrincipal User user, + @RequestParam("nickname") @NicknameConstraint String nickname) { return ResponseEntity .status(OK) - .body(userService.getProfileStatus(user)); + .body(userService.isNicknameAvailable(user, nickname)); } - @PatchMapping("/profile-status") - public ResponseEntity editProfileStatus(Principal principal, - @Valid @RequestBody EditProfileStatusRequest editProfileStatusRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - userService.editProfileStatus(user, editProfileStatusRequest); + @PostMapping("/profile") + @PreAuthorize("isAuthenticated()") + public ResponseEntity registerUserInfo(@AuthenticationPrincipal User user, + @Valid @RequestBody RegisterUserInfoRequest registerUserInfoRequest) { + userService.registerUserInfo(user, registerUserInfoRequest); return ResponseEntity - .status(NO_CONTENT) + .status(CREATED) .build(); } - @PostMapping("/login") - public ResponseEntity login(@RequestBody String userId) { - LoginResponse response = userService.login(Long.valueOf(userId)); + @GetMapping("/profile/{userId}") + public ResponseEntity getProfileInfo(@AuthenticationPrincipal User user, + @PathVariable("userId") Long userId) { return ResponseEntity .status(OK) - .body(response); + .body(userService.getProfileInfo(user, userId)); } @GetMapping("/my-profile") - public ResponseEntity getMyProfileInfo(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @PreAuthorize("isAuthenticated()") + public ResponseEntity getMyProfileInfo(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(userService.getMyProfileInfo(user)); } @PatchMapping("/my-profile") - public ResponseEntity updateMyProfileInfo(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity updateMyProfileInfo(@AuthenticationPrincipal User user, @RequestBody @Valid UpdateMyProfileRequest updateMyProfileRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); userService.updateMyProfileInfo(user, updateMyProfileRequest); return ResponseEntity .status(NO_CONTENT) .build(); } - @GetMapping("/profile/{userId}") - public ResponseEntity getProfileInfo(Principal principal, - @PathVariable("userId") Long userId) { - User user = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); + @GetMapping("/profile-status") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getProfileStatus(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) - .body(userService.getProfileInfo(user, userId)); + .body(userService.getProfileStatus(user)); } - @PostMapping("/profile") - public ResponseEntity registerUserInfo(Principal principal, - @Valid @RequestBody RegisterUserInfoRequest registerUserInfoRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - userService.registerUserInfo(user, registerUserInfoRequest); + @PatchMapping("/profile-status") + @PreAuthorize("isAuthenticated()") + public ResponseEntity editProfileStatus(@AuthenticationPrincipal User user, + @Valid @RequestBody EditProfileStatusRequest editProfileStatusRequest) { + userService.editProfileStatus(user, editProfileStatusRequest); return ResponseEntity - .status(CREATED) + .status(NO_CONTENT) .build(); } - @GetMapping("/{userId}/user-novel-stats") - public ResponseEntity getUserNovelStatistics(@PathVariable("userId") Long userId) { + @GetMapping("/me") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getUserIdAndNicknameAndGender(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) - .body(userNovelService.getUserNovelStatistics(userId)); + .body(userService.getUserIdAndNicknameAndGender(user)); } @GetMapping("/{userId}/novels") - public ResponseEntity getUserNovelsAndNovels(Principal principal, + public ResponseEntity getUserNovelsAndNovels(@AuthenticationPrincipal User visitor, @PathVariable("userId") Long userId, @RequestParam("readStatus") String readStatus, @RequestParam("lastUserNovelId") Long lastUserNovelId, @RequestParam("size") int size, @RequestParam("sortType") String sortType) { - User visitor = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); return ResponseEntity .status(OK) .body(userNovelService.getUserNovelsAndNovels( @@ -162,24 +158,19 @@ public ResponseEntity getUserNovelsAndNovels(Prin } @GetMapping("/{userId}/feeds") - public ResponseEntity getUserFeeds(Principal principal, + public ResponseEntity getUserFeeds(@AuthenticationPrincipal User visitor, @PathVariable("userId") Long userId, @RequestParam("lastFeedId") Long lastFeedId, @RequestParam("size") int size) { - User visitor = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); return ResponseEntity .status(OK) .body(feedService.getUserFeeds(visitor, userId, lastFeedId, size)); } @GetMapping("/{userId}/preferences/genres") - public ResponseEntity getUserGenrePreferences(Principal principal, - @PathVariable("userId") Long ownerId) { - User visitor = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); + public ResponseEntity getUserGenrePreferences( + @AuthenticationPrincipal User visitor, + @PathVariable("userId") Long ownerId) { return ResponseEntity .status(OK) .body(userNovelService.getUserGenrePreferences(visitor, ownerId)); @@ -187,73 +178,69 @@ public ResponseEntity getUserGenrePreferences(P @GetMapping("/{userId}/preferences/attractive-points") public ResponseEntity - getUserAttractivePointsAndKeywords(Principal principal, + getUserAttractivePointsAndKeywords(@AuthenticationPrincipal User visitor, @PathVariable("userId") Long ownerId) { - User visitor = principal == null - ? null - : userService.getUserOrException(Long.valueOf(principal.getName())); return ResponseEntity .status(OK) .body(userNovelService.getUserAttractivePointsAndKeywords(visitor, ownerId)); } - @PutMapping("/info") - public ResponseEntity editMyInfo(Principal principal, - @Valid @RequestBody EditMyInfoRequest editMyInfoRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - userService.editMyInfo(user, editMyInfoRequest); + @GetMapping("/{userId}/user-novel-stats") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getUserNovelStatistics(@PathVariable("userId") Long userId) { return ResponseEntity - .status(NO_CONTENT) - .build(); + .status(OK) + .body(userNovelService.getUserNovelStatistics(userId)); } - @GetMapping("/me") - public ResponseEntity getUserIdAndNicknameAndGender(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @GetMapping("/info") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getUserInfo(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) - .body(userService.getUserIdAndNicknameAndGender(user)); - } - - @PostMapping("/fcm-token") - public ResponseEntity registerFCMToken(Principal principal, - @Valid @RequestBody FCMTokenRequest fcmTokenRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - return userService.registerFCMToken(user, fcmTokenRequest) - ? ResponseEntity.status(CREATED).build() - : ResponseEntity.status(NO_CONTENT).build(); + .body(userService.getUserInfo(user)); } - @GetMapping("/push-settings") - public ResponseEntity getPushSettingValue(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @PutMapping("/info") + @PreAuthorize("isAuthenticated()") + public ResponseEntity editMyInfo(@AuthenticationPrincipal User user, + @Valid @RequestBody EditMyInfoRequest editMyInfoRequest) { + userService.editMyInfo(user, editMyInfoRequest); return ResponseEntity - .status(OK) - .body(userService.getPushSettingValue(user)); + .status(NO_CONTENT) + .build(); } @PostMapping("/push-settings") - public ResponseEntity registerPushSetting(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity registerPushSetting(@AuthenticationPrincipal User user, @Valid @RequestBody PushSettingRequest pushSettingRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); userService.registerPushSetting(user, pushSettingRequest.isPushEnabled()); return ResponseEntity .status(NO_CONTENT) .build(); } + @GetMapping("/push-settings") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getPushSettingValue(@AuthenticationPrincipal User user) { + return ResponseEntity + .status(OK) + .body(userService.getPushSettingValue(user)); + } + @GetMapping("/terms-settings") - public ResponseEntity getTermsSettingValue(Principal principal) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @PreAuthorize("isAuthenticated()") + public ResponseEntity getTermsSettingValue(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(userService.getTermsSettingValue(user)); } @PatchMapping("/terms-settings") - public ResponseEntity updateTermsSetting(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity updateTermsSetting(@AuthenticationPrincipal User user, @Valid @RequestBody TermsSettingRequest termsSettingRequest) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); userService.updateTermsSetting(user, termsSettingRequest.serviceAgreed(), termsSettingRequest.privacyAgreed(), termsSettingRequest.marketingAgreed()); return ResponseEntity diff --git a/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java b/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java index 484bfd6de..09b353b2d 100644 --- a/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java +++ b/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java @@ -5,9 +5,10 @@ import static org.springframework.http.HttpStatus.OK; import jakarta.validation.Valid; -import java.security.Principal; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -23,59 +24,53 @@ import org.websoso.WSSServer.dto.userNovel.UserNovelUpdateRequest; import org.websoso.WSSServer.service.NovelService; import org.websoso.WSSServer.service.UserNovelService; -import org.websoso.WSSServer.service.UserService; @RequestMapping("/user-novels") @RestController @RequiredArgsConstructor public class UserNovelController { - private final UserService userService; private final NovelService novelService; private final UserNovelService userNovelService; - @GetMapping("/{novelId}") - public ResponseEntity getEvaluation(Principal principal, @PathVariable Long novelId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - Novel novel = novelService.getNovelOrException(novelId); - - return ResponseEntity - .status(OK) - .body(userNovelService.getEvaluation(user, novel)); - } - @PostMapping - public ResponseEntity createEvaluation(Principal principal, + @PreAuthorize("isAuthenticated()") + public ResponseEntity createEvaluation(@AuthenticationPrincipal User user, @Valid @RequestBody UserNovelCreateRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); userNovelService.createEvaluation(user, request); - return ResponseEntity .status(CREATED) .build(); } - @PutMapping("/{novelId}") - public ResponseEntity updateEvaluation(Principal principal, @PathVariable Long novelId, - @Valid @RequestBody UserNovelUpdateRequest request) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); + @GetMapping("/{novelId}") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getEvaluation(@AuthenticationPrincipal User user, + @PathVariable Long novelId) { Novel novel = novelService.getNovelOrException(novelId); - userNovelService.updateEvaluation(user, novel, request); + return ResponseEntity + .status(OK) + .body(userNovelService.getEvaluation(user, novel)); + } + @PutMapping("/{novelId}") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.UserNovel))") + public ResponseEntity updateEvaluation(@AuthenticationPrincipal User user, + @PathVariable Long novelId, + @Valid @RequestBody UserNovelUpdateRequest request) { + userNovelService.updateEvaluation(user, novelId, request); return ResponseEntity .status(NO_CONTENT) .build(); } @DeleteMapping("/{novelId}") - public ResponseEntity deleteEvaluation(Principal principal, @PathVariable Long novelId) { - User user = userService.getUserOrException(Long.valueOf(principal.getName())); - Novel novel = novelService.getNovelOrException(novelId); - userNovelService.deleteEvaluation(user, novel); - + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.UserNovel))") + public ResponseEntity deleteEvaluation(@AuthenticationPrincipal User user, + @PathVariable Long novelId) { + userNovelService.deleteEvaluation(user, novelId); return ResponseEntity .status(NO_CONTENT) .build(); } - } diff --git a/src/main/java/org/websoso/WSSServer/domain/Feed.java b/src/main/java/org/websoso/WSSServer/domain/Feed.java index 2d5355264..414e9b121 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Feed.java +++ b/src/main/java/org/websoso/WSSServer/domain/Feed.java @@ -2,7 +2,6 @@ import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.GenerationType.IDENTITY; -import static org.websoso.WSSServer.exception.error.CustomUserError.INVALID_AUTHORIZED; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -18,12 +17,9 @@ import java.util.List; import java.util.Objects; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.DynamicInsert; -import org.websoso.WSSServer.domain.common.Action; -import org.websoso.WSSServer.exception.exception.CustomUserException; @Getter @DynamicInsert @@ -73,14 +69,15 @@ public class Feed { @OneToOne(mappedBy = "feed", cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true) private PopularFeed popularFeed; - @Builder - public Feed(String feedContent, Boolean isSpoiler, Long novelId, User user) { + private Feed(String feedContent, Long novelId, Boolean isSpoiler, User user) { this.feedContent = feedContent; - this.isSpoiler = isSpoiler; this.novelId = novelId; + this.isSpoiler = isSpoiler; this.user = user; - this.createdDate = LocalDateTime.now(); - this.modifiedDate = this.createdDate; + } + + public static Feed create(String feedContent, Long novelId, Boolean isSpoiler, User user) { + return new Feed(feedContent, novelId, isSpoiler, user); } public void updateFeed(String feedContent, Boolean isSpoiler, Long novelId) { @@ -90,13 +87,6 @@ public void updateFeed(String feedContent, Boolean isSpoiler, Long novelId) { this.modifiedDate = LocalDateTime.now(); } - public void validateUserAuthorization(User user, Action action) { - if (!this.user.equals(user)) { - throw new CustomUserException(INVALID_AUTHORIZED, - "only the author can " + action.getLabel() + " the feed"); - } - } - public boolean isNovelChanged(Long novelId) { return !Objects.equals(this.novelId, novelId); } @@ -105,4 +95,11 @@ public void hideFeed() { this.isHidden = true; } + public Long getWriterId() { + return user.getUserId(); + } + + public boolean isMine(Long userId) { + return this.user.getUserId().equals(userId); + } } diff --git a/src/main/java/org/websoso/WSSServer/domain/Notice.java b/src/main/java/org/websoso/WSSServer/domain/Notice.java deleted file mode 100644 index 6109c41fd..000000000 --- a/src/main/java/org/websoso/WSSServer/domain/Notice.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.websoso.WSSServer.domain; - -import static jakarta.persistence.GenerationType.IDENTITY; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.websoso.WSSServer.domain.common.BaseEntity; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Notice extends BaseEntity { - - @Id - @GeneratedValue(strategy = IDENTITY) - @Column(nullable = false) - private Long noticeId; - - @Column(columnDefinition = "varchar(200)", nullable = false) - private String noticeTitle; - - @Column(columnDefinition = "varchar(2000)", nullable = false) - private String noticeContent; - - @Column - private Long userId; - - @Builder - private Notice(String noticeTitle, String noticeContent, Long userId) { - this.noticeTitle = noticeTitle; - this.noticeContent = noticeContent; - this.userId = userId; - } - - public void updateNotice(String noticeTitle, String noticeContent, Long userId) { - this.noticeTitle = noticeTitle; - this.noticeContent = noticeContent; - this.userId = userId; - } - -} diff --git a/src/main/java/org/websoso/WSSServer/dto/notice/NoticeEditRequest.java b/src/main/java/org/websoso/WSSServer/dto/notice/NoticeEditRequest.java deleted file mode 100644 index fdd33d36f..000000000 --- a/src/main/java/org/websoso/WSSServer/dto/notice/NoticeEditRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.websoso.WSSServer.dto.notice; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import org.websoso.WSSServer.validation.ZeroAllowedUserIdConstraint; - -public record NoticeEditRequest( - - @NotBlank(message = "공지 제목은 비어 있거나, 공백일 수 없습니다.") - @Size(max = 200, message = "공지 제목은 200자를 초과할 수 없습니다.") - String noticeTitle, - - @NotBlank(message = "공지 내용은 비어 있거나, 공백일 수 없습니다.") - @Size(max = 2000, message = "공지 내용은 2000자를 초과할 수 없습니다.") - String noticeContent, - - @ZeroAllowedUserIdConstraint - Long userId -) { -} diff --git a/src/main/java/org/websoso/WSSServer/dto/notice/NoticeGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/notice/NoticeGetResponse.java deleted file mode 100644 index 551f1144b..000000000 --- a/src/main/java/org/websoso/WSSServer/dto/notice/NoticeGetResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.websoso.WSSServer.dto.notice; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import org.websoso.WSSServer.domain.Notice; - -public record NoticeGetResponse( - String noticeTitle, - String noticeContent, - String createdDate -) { - - public static NoticeGetResponse from(Notice notice) { - return new NoticeGetResponse( - notice.getNoticeTitle(), - notice.getNoticeContent(), - formatDateString(notice.getCreatedDate()) - ); - } - - private static String formatDateString(LocalDateTime dateTime) { - return dateTime.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); - } -} diff --git a/src/main/java/org/websoso/WSSServer/dto/notice/NoticePostRequest.java b/src/main/java/org/websoso/WSSServer/dto/notice/NoticePostRequest.java deleted file mode 100644 index 0e26947aa..000000000 --- a/src/main/java/org/websoso/WSSServer/dto/notice/NoticePostRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.websoso.WSSServer.dto.notice; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import org.websoso.WSSServer.validation.ZeroAllowedUserIdConstraint; - -public record NoticePostRequest( - - @NotBlank(message = "공지 제목은 비어 있거나, 공백일 수 없습니다.") - @Size(max = 200, message = "공지 제목은 200자를 초과할 수 없습니다.") - String noticeTitle, - - @NotBlank(message = "공지 내용은 비어 있거나, 공백일 수 없습니다.") - @Size(max = 2000, message = "공지 내용은 2000자를 초과할 수 없습니다.") - String noticeContent, - - @ZeroAllowedUserIdConstraint - Long userId -) { -} diff --git a/src/main/java/org/websoso/WSSServer/dto/notice/NoticesGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/notice/NoticesGetResponse.java deleted file mode 100644 index 27f60769a..000000000 --- a/src/main/java/org/websoso/WSSServer/dto/notice/NoticesGetResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.websoso.WSSServer.dto.notice; - -import java.util.List; -import java.util.stream.Collectors; -import org.websoso.WSSServer.domain.Notice; - -public record NoticesGetResponse( - List notices -) { - - public static NoticesGetResponse of(List notices) { - List noticeList = notices.stream() - .map(NoticeGetResponse::from) - .collect(Collectors.toList()); - return new NoticesGetResponse(noticeList); - } -} diff --git a/src/main/java/org/websoso/WSSServer/dto/notification/NotificationCreateRequest.java b/src/main/java/org/websoso/WSSServer/dto/notification/NotificationCreateRequest.java index 19bad9d32..4369a3bac 100644 --- a/src/main/java/org/websoso/WSSServer/dto/notification/NotificationCreateRequest.java +++ b/src/main/java/org/websoso/WSSServer/dto/notification/NotificationCreateRequest.java @@ -3,6 +3,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import org.websoso.WSSServer.validation.NotificationTypeConstraint; import org.websoso.WSSServer.validation.ZeroAllowedUserIdConstraint; public record NotificationCreateRequest( @@ -24,6 +25,7 @@ public record NotificationCreateRequest( Long userId, @NotBlank(message = "알림 타입 이름은 필수입니다.") + @NotificationTypeConstraint String notificationTypeName ) { } diff --git a/src/main/java/org/websoso/WSSServer/exception/error/CustomAuthorizationError.java b/src/main/java/org/websoso/WSSServer/exception/error/CustomAuthorizationError.java new file mode 100644 index 000000000..e30fa3878 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/exception/error/CustomAuthorizationError.java @@ -0,0 +1,19 @@ +package org.websoso.WSSServer.exception.error; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.websoso.WSSServer.exception.common.ICustomError; + +@Getter +@AllArgsConstructor +public enum CustomAuthorizationError implements ICustomError { + + UNSUPPORTED_RESOURCE_TYPE("AUTHORIZATION-001", "지원하지 않는 리소스 타입입니다.", BAD_REQUEST); + + private final String code; + private final String description; + private final HttpStatus statusCode; +} diff --git a/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java b/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java index 96a0989f6..f93b15db7 100644 --- a/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java +++ b/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java @@ -16,7 +16,7 @@ public enum CustomFeedError implements ICustomError { FEED_NOT_FOUND("FEED-001", "해당 ID를 가진 피드를 찾을 수 없습니다.", NOT_FOUND), ALREADY_LIKED("FEED-002", "이미 해당 피드에 좋아요를 눌렀습니다.", CONFLICT), - LIKE_USER_NOT_FOUND("FEED-003", "해당 사용자가 이 피드에 좋아요를 누르지 않았습니다.", NOT_FOUND), + NOT_LIKED("FEED-003", "해당 사용자가 이 피드에 좋아요를 누르지 않았습니다.", NOT_FOUND), INVALID_LIKE_COUNT("FEED-004", "좋아요 수가 유효하지 않습니다.", BAD_REQUEST), HIDDEN_FEED_ACCESS("FEED-005", "이 피드는 숨겨져 있어 접근할 수 없습니다.", FORBIDDEN), BLOCKED_USER_ACCESS("FEED-006", "해당 사용자와 피드 작성자가 차단 상태이므로 이 피드에 접근할 수 없습니다.", FORBIDDEN), diff --git a/src/main/java/org/websoso/WSSServer/exception/error/CustomNoticeError.java b/src/main/java/org/websoso/WSSServer/exception/error/CustomNoticeError.java deleted file mode 100644 index 70e5fda29..000000000 --- a/src/main/java/org/websoso/WSSServer/exception/error/CustomNoticeError.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.websoso.WSSServer.exception.error; - -import static org.springframework.http.HttpStatus.FORBIDDEN; -import static org.springframework.http.HttpStatus.NOT_FOUND; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.http.HttpStatus; -import org.websoso.WSSServer.exception.common.ICustomError; - -@AllArgsConstructor -@Getter -public enum CustomNoticeError implements ICustomError { - - NOTICE_FORBIDDEN("NOTICE-001", "관리자가 아닌 계정은 공지사항을 작성 혹은 수정 혹은 삭제할 수 없습니다.", FORBIDDEN), - NOTICE_NOT_FOUND("NOTICE-002", "해당 ID를 가진 공지사항을 찾을 수 없습니다.", NOT_FOUND); - - private final String code; - private final String description; - private final HttpStatus statusCode; -} diff --git a/src/main/java/org/websoso/WSSServer/exception/exception/CustomAuthorizationException.java b/src/main/java/org/websoso/WSSServer/exception/exception/CustomAuthorizationException.java new file mode 100644 index 000000000..6365713c2 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/exception/exception/CustomAuthorizationException.java @@ -0,0 +1,12 @@ +package org.websoso.WSSServer.exception.exception; + +import lombok.Getter; +import org.websoso.WSSServer.exception.common.AbstractCustomException; +import org.websoso.WSSServer.exception.error.CustomAuthorizationError; + +@Getter +public class CustomAuthorizationException extends AbstractCustomException { + public CustomAuthorizationException(CustomAuthorizationError customAuthorizationError, String message) { + super(customAuthorizationError, message); + } +} diff --git a/src/main/java/org/websoso/WSSServer/exception/exception/CustomNoticeException.java b/src/main/java/org/websoso/WSSServer/exception/exception/CustomNoticeException.java deleted file mode 100644 index 920c6da9a..000000000 --- a/src/main/java/org/websoso/WSSServer/exception/exception/CustomNoticeException.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.websoso.WSSServer.exception.exception; - -import lombok.Getter; -import org.websoso.WSSServer.exception.common.AbstractCustomException; -import org.websoso.WSSServer.exception.error.CustomNoticeError; - -@Getter -public class CustomNoticeException extends AbstractCustomException { - - public CustomNoticeException(CustomNoticeError customNoticeError, String message) { - super(customNoticeError, message); - } -} diff --git a/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java b/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java index 8aab4cb92..d72be14bf 100644 --- a/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java +++ b/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java @@ -48,8 +48,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; +import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; import org.websoso.WSSServer.config.jwt.JwtProvider; -import org.websoso.WSSServer.config.jwt.UserAuthentication; import org.websoso.WSSServer.domain.RefreshToken; import org.websoso.WSSServer.domain.User; import org.websoso.WSSServer.domain.UserAppleToken; @@ -284,9 +284,9 @@ private AuthResponse authenticate(String socialId, String email, String nickname userAppleTokenRepository.save(UserAppleToken.create(user, appleRefreshToken)); } - UserAuthentication userAuthentication = new UserAuthentication(user.getUserId(), null, null); - String accessToken = jwtProvider.generateAccessToken(userAuthentication); - String refreshToken = jwtProvider.generateRefreshToken(userAuthentication); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, null); + String accessToken = jwtProvider.generateAccessToken(customAuthenticationToken); + String refreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); refreshTokenRepository.save(new RefreshToken(refreshToken, user.getUserId())); diff --git a/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java b/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java index b7232b05e..eb46e9c5f 100644 --- a/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java +++ b/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java @@ -12,8 +12,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; +import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; import org.websoso.WSSServer.config.jwt.JwtProvider; -import org.websoso.WSSServer.config.jwt.UserAuthentication; import org.websoso.WSSServer.domain.RefreshToken; import org.websoso.WSSServer.domain.User; import org.websoso.WSSServer.dto.auth.AuthResponse; @@ -70,9 +70,9 @@ public AuthResponse getUserInfoFromKakao(String kakaoAccessToken) { user = userRepository.save(User.createBySocial(socialId, defaultNickname, kakaoUserInfo.email())); } - UserAuthentication userAuthentication = new UserAuthentication(user.getUserId(), null, null); - String accessToken = jwtProvider.generateAccessToken(userAuthentication); - String refreshToken = jwtProvider.generateRefreshToken(userAuthentication); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, null); + String accessToken = jwtProvider.generateAccessToken(customAuthenticationToken); + String refreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); RefreshToken redisRefreshToken = new RefreshToken(refreshToken, user.getUserId()); refreshTokenRepository.save(redisRefreshToken); diff --git a/src/main/java/org/websoso/WSSServer/repository/LikeRepository.java b/src/main/java/org/websoso/WSSServer/repository/LikeRepository.java index 62a87160e..2cc3f53bb 100644 --- a/src/main/java/org/websoso/WSSServer/repository/LikeRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/LikeRepository.java @@ -1,5 +1,6 @@ package org.websoso.WSSServer.repository; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import org.websoso.WSSServer.domain.Feed; @@ -8,7 +9,7 @@ @Repository public interface LikeRepository extends JpaRepository { - void deleteByUserIdAndFeed(Long userId, Feed feed); + Optional findByUserIdAndFeed(Long userId, Feed feed); boolean existsByUserIdAndFeed(Long userId, Feed feed); } diff --git a/src/main/java/org/websoso/WSSServer/repository/NoticeRepository.java b/src/main/java/org/websoso/WSSServer/repository/NoticeRepository.java deleted file mode 100644 index bfe3c17de..000000000 --- a/src/main/java/org/websoso/WSSServer/repository/NoticeRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.websoso.WSSServer.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Notice; - -@Repository -public interface NoticeRepository extends JpaRepository { -} diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java index 09067260b..e0fdda963 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java @@ -27,4 +27,6 @@ public interface UserNovelRepository extends JpaRepository, Use List findByUserAndIsInterestTrue(User user); List findUserNovelByUser(User user); + + Optional findByNovel_NovelIdAndUser(Long novelId, User user); } diff --git a/src/main/java/org/websoso/WSSServer/service/AuthService.java b/src/main/java/org/websoso/WSSServer/service/AuthService.java index 80767fc78..4cf640385 100644 --- a/src/main/java/org/websoso/WSSServer/service/AuthService.java +++ b/src/main/java/org/websoso/WSSServer/service/AuthService.java @@ -5,10 +5,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; import org.websoso.WSSServer.config.jwt.JWTUtil; import org.websoso.WSSServer.config.jwt.JwtProvider; import org.websoso.WSSServer.config.jwt.JwtValidationType; -import org.websoso.WSSServer.config.jwt.UserAuthentication; import org.websoso.WSSServer.domain.RefreshToken; import org.websoso.WSSServer.dto.auth.ReissueResponse; import org.websoso.WSSServer.exception.exception.CustomAuthException; @@ -32,9 +32,9 @@ public ReissueResponse reissue(String refreshToken) { } Long userId = jwtUtil.getUserIdFromJwt(refreshToken); - UserAuthentication userAuthentication = new UserAuthentication(userId, null, null); - String newAccessToken = jwtProvider.generateAccessToken(userAuthentication); - String newRefreshToken = jwtProvider.generateRefreshToken(userAuthentication); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(userId, null, null); + String newAccessToken = jwtProvider.generateAccessToken(customAuthenticationToken); + String newRefreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); refreshTokenRepository.delete(storedRefreshToken); refreshTokenRepository.save(new RefreshToken(newRefreshToken, userId)); diff --git a/src/main/java/org/websoso/WSSServer/service/BlockService.java b/src/main/java/org/websoso/WSSServer/service/BlockService.java index b4dfbe216..9413ab05d 100644 --- a/src/main/java/org/websoso/WSSServer/service/BlockService.java +++ b/src/main/java/org/websoso/WSSServer/service/BlockService.java @@ -2,9 +2,7 @@ import static org.websoso.WSSServer.domain.common.Role.ADMIN; import static org.websoso.WSSServer.exception.error.CustomBlockError.ALREADY_BLOCKED; -import static org.websoso.WSSServer.exception.error.CustomBlockError.BLOCK_NOT_FOUND; import static org.websoso.WSSServer.exception.error.CustomBlockError.CANNOT_ADMIN_BLOCK; -import static org.websoso.WSSServer.exception.error.CustomBlockError.INVALID_AUTHORIZED_BLOCK; import static org.websoso.WSSServer.exception.error.CustomBlockError.SELF_BLOCKED; import java.util.List; @@ -21,14 +19,13 @@ @Service @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional public class BlockService { private final UserService userService; private final AvatarService avatarService; private final BlockRepository blockRepository; - @Transactional public void block(User blocker, Long blockedId) { User blockedUser = userService.getUserOrException(blockedId); if (blockedUser.getRole() == ADMIN) { @@ -47,6 +44,7 @@ public void block(User blocker, Long blockedId) { blockRepository.save(Block.create(blockingId, blockedId)); } + @Transactional(readOnly = true) public BlocksGetResponse getBlockList(User user) { List blocks = blockRepository.findByBlockingId(user.getUserId()); List blockGetResponses = blocks.stream() @@ -58,17 +56,11 @@ public BlocksGetResponse getBlockList(User user) { return new BlocksGetResponse(blockGetResponses); } - @Transactional - public void deleteBlock(User user, Long blockId) { - Block block = blockRepository.findById(blockId).orElseThrow(() -> - new CustomBlockException(BLOCK_NOT_FOUND, "block with the given blockId was not found")); - if (!block.getBlockingId().equals(user.getUserId())) { - throw new CustomBlockException(INVALID_AUTHORIZED_BLOCK, - "block with the given blockId is not from user with the given userId"); - } - blockRepository.delete(block); + public void deleteBlock(Long blockId) { + blockRepository.deleteById(blockId); } + @Transactional(readOnly = true) public boolean isBlocked(Long blockingId, Long blockedId) { return blockRepository.existsByBlockingIdAndBlockedId(blockingId, blockedId); } diff --git a/src/main/java/org/websoso/WSSServer/service/FeedService.java b/src/main/java/org/websoso/WSSServer/service/FeedService.java index 140570c97..63e06409a 100644 --- a/src/main/java/org/websoso/WSSServer/service/FeedService.java +++ b/src/main/java/org/websoso/WSSServer/service/FeedService.java @@ -1,12 +1,8 @@ package org.websoso.WSSServer.service; import static java.lang.Boolean.TRUE; -import static org.websoso.WSSServer.domain.common.Action.DELETE; -import static org.websoso.WSSServer.domain.common.Action.UPDATE; import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.REPORT; -import static org.websoso.WSSServer.exception.error.CustomFeedError.BLOCKED_USER_ACCESS; import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; -import static org.websoso.WSSServer.exception.error.CustomFeedError.HIDDEN_FEED_ACCESS; import static org.websoso.WSSServer.exception.error.CustomFeedError.SELF_REPORT_NOT_ALLOWED; import static org.websoso.WSSServer.exception.error.CustomUserError.PRIVATE_PROFILE_STATUS; @@ -15,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -84,22 +81,15 @@ public class FeedService { private final NotificationRepository notificationRepository; public void createFeed(User user, FeedCreateRequest request) { - if (request.novelId() != null) { - novelService.getNovelOrException(request.novelId()); - } - Feed feed = Feed.builder() - .feedContent(request.feedContent()) - .isSpoiler(request.isSpoiler()) - .novelId(request.novelId()) - .user(user) - .build(); + Optional.ofNullable(request.novelId()) + .ifPresent(novelService::getNovelOrException); + Feed feed = Feed.create(request.feedContent(), request.novelId(), request.isSpoiler(), user); feedRepository.save(feed); feedCategoryService.createFeedCategory(feed, request.relevantCategories()); } - public void updateFeed(User user, Long feedId, FeedUpdateRequest request) { + public void updateFeed(Long feedId, FeedUpdateRequest request) { Feed feed = getFeedOrException(feedId); - feed.validateUserAuthorization(user, UPDATE); if (request.novelId() != null && feed.isNovelChanged(request.novelId())) { novelService.getNovelOrException(request.novelId()); @@ -108,19 +98,14 @@ public void updateFeed(User user, Long feedId, FeedUpdateRequest request) { feedCategoryService.updateFeedCategory(feed, request.relevantCategories()); } - public void deleteFeed(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - feed.validateUserAuthorization(user, DELETE); - feedRepository.delete(feed); + public void deleteFeed(Long feedId) { + feedRepository.deleteById(feedId); } public void likeFeed(User user, Long feedId) { Feed feed = getFeedOrException(feedId); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); likeService.createLike(user, feed); - if (feed.getLikes().size() == POPULAR_FEED_LIKE_THRESHOLD) { popularFeedService.createPopularFeed(feed); } @@ -191,17 +176,12 @@ private String createNotificationTitle(Feed feed) { public void unLikeFeed(User user, Long feedId) { Feed feed = getFeedOrException(feedId); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); likeService.deleteLike(user, feed); } @Transactional(readOnly = true) public FeedGetResponse getFeedById(User user, Long feedId) { Feed feed = getFeedOrException(feedId); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - UserBasicInfo userBasicInfo = getUserBasicInfo(feed.getUser()); Novel novel = getLinkedNovelOrNull(feed.getNovelId()); Boolean isLiked = isUserLikedFeed(user, feed); @@ -225,35 +205,28 @@ public FeedsGetResponse getFeeds(User user, String category, Long lastFeedId, in public void createComment(User user, Long feedId, CommentCreateRequest request) { Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); commentService.createComment(user, feed, request.commentContent()); } public void updateComment(User user, Long feedId, Long commentId, CommentUpdateRequest request) { Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); commentService.updateComment(user.getUserId(), feed, commentId, request.commentContent()); } public void deleteComment(User user, Long feedId, Long commentId) { Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); commentService.deleteComment(user.getUserId(), feed, commentId); } @Transactional(readOnly = true) public CommentsGetResponse getComments(User user, Long feedId) { Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); return commentService.getComments(user, feed); } public void reportFeed(User user, Long feedId, ReportedType reportedType) { Feed feed = getFeedOrException(feedId); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - if (isUserFeedOwner(feed.getUser(), user)) { throw new CustomFeedException(SELF_REPORT_NOT_ALLOWED, "cannot report own feed"); } @@ -273,10 +246,6 @@ public void reportFeed(User user, Long feedId, ReportedType reportedType) { public void reportComment(User user, Long feedId, Long commentId, ReportedType reportedType) { Feed feed = getFeedOrException(feedId); - - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - commentService.createReportedComment(feed, commentId, user, reportedType); } @@ -285,19 +254,6 @@ private Feed getFeedOrException(Long feedId) { new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); } - private void checkHiddenFeed(Feed feed) { - if (feed.getIsHidden()) { - throw new CustomFeedException(HIDDEN_FEED_ACCESS, "Cannot access hidden feed."); - } - } - - private void checkBlocked(User createdFeedUser, User user) { - if (blockService.isBlocked(user.getUserId(), createdFeedUser.getUserId())) { - throw new CustomFeedException(BLOCKED_USER_ACCESS, - "cannot access this feed because either you or the feed author has blocked the other."); - } - } - private UserBasicInfo getUserBasicInfo(User user) { return user.getUserBasicInfo( avatarService.getAvatarOrException(user.getAvatarId()).getAvatarImage() @@ -390,14 +346,6 @@ public NovelGetResponseFeedTab getFeedsByNovel(User user, Long novelId, Long las return NovelGetResponseFeedTab.of(feeds.hasNext(), feedGetResponses); } - private void validateFeedAccess(Feed feed, User user) { - if (feed.getUser().equals(user)) { - return; - } - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - } - @Transactional(readOnly = true) public UserFeedsGetResponse getUserFeeds(User visitor, Long ownerId, Long lastFeedId, int size) { User owner = userService.getUserOrException(ownerId); diff --git a/src/main/java/org/websoso/WSSServer/service/LikeService.java b/src/main/java/org/websoso/WSSServer/service/LikeService.java index d728dc53e..a981c5a8e 100644 --- a/src/main/java/org/websoso/WSSServer/service/LikeService.java +++ b/src/main/java/org/websoso/WSSServer/service/LikeService.java @@ -1,6 +1,7 @@ package org.websoso.WSSServer.service; import static org.websoso.WSSServer.exception.error.CustomFeedError.ALREADY_LIKED; +import static org.websoso.WSSServer.exception.error.CustomFeedError.NOT_LIKED; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -26,7 +27,14 @@ public void createLike(User user, Feed feed) { } public void deleteLike(User user, Feed feed) { - likeRepository.deleteByUserIdAndFeed(user.getUserId(), feed); + Like like = getLikeOrException(user, feed); + likeRepository.delete(like); + } + + private Like getLikeOrException(User user, Feed feed) { + return likeRepository.findByUserIdAndFeed(user.getUserId(), feed) + .orElseThrow(() -> new CustomFeedException(NOT_LIKED, + "User did not like this feed or like already deleted")); } @Transactional(readOnly = true) diff --git a/src/main/java/org/websoso/WSSServer/service/NoticeService.java b/src/main/java/org/websoso/WSSServer/service/NoticeService.java deleted file mode 100644 index d4ad8569d..000000000 --- a/src/main/java/org/websoso/WSSServer/service/NoticeService.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.domain.common.Role.ADMIN; -import static org.websoso.WSSServer.exception.error.CustomNoticeError.NOTICE_FORBIDDEN; -import static org.websoso.WSSServer.exception.error.CustomNoticeError.NOTICE_NOT_FOUND; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Notice; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.common.Role; -import org.websoso.WSSServer.dto.notice.NoticeEditRequest; -import org.websoso.WSSServer.dto.notice.NoticePostRequest; -import org.websoso.WSSServer.exception.exception.CustomNoticeException; -import org.websoso.WSSServer.repository.NoticeRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class NoticeService { - - private final NoticeRepository noticeRepository; - private static final Role ADMIN_ROLE = ADMIN; - - public void createNotice(User user, NoticePostRequest noticePostRequest) { - validateAuthorization(user); - noticeRepository.save(Notice.builder() - .noticeTitle(noticePostRequest.noticeTitle()) - .noticeContent(noticePostRequest.noticeContent()) - .userId(noticePostRequest.userId()) - .build()); - } - - public void editNotice(User user, Long noticeId, NoticeEditRequest noticeEditRequest) { - validateAuthorization(user); - Notice notice = getNoticeOrException(noticeId); - notice.updateNotice(noticeEditRequest.noticeTitle(), noticeEditRequest.noticeContent(), - noticeEditRequest.userId()); - } - - private static void validateAuthorization(User user) { - if (user.getRole() != ADMIN_ROLE) { - throw new CustomNoticeException(NOTICE_FORBIDDEN, - "user who tried to create or modify or delete the notice is not ADMIN"); - } - } - - public void deleteNotice(User user, Long noticeId) { - validateAuthorization(user); - Notice notice = getNoticeOrException(noticeId); - noticeRepository.delete(notice); - } - - private Notice getNoticeOrException(Long noticeId) { - return noticeRepository.findById(noticeId).orElseThrow(() -> - new CustomNoticeException(NOTICE_NOT_FOUND, "notice with given noticeId was not found")); - } -} diff --git a/src/main/java/org/websoso/WSSServer/service/NotificationService.java b/src/main/java/org/websoso/WSSServer/service/NotificationService.java index a8d617b8c..d51867538 100644 --- a/src/main/java/org/websoso/WSSServer/service/NotificationService.java +++ b/src/main/java/org/websoso/WSSServer/service/NotificationService.java @@ -1,13 +1,32 @@ package org.websoso.WSSServer.service; +import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.FEED; +import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.NOTICE; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_ALREADY_READ; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_READ_FORBIDDEN; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_TYPE_INVALID; +import static org.websoso.WSSServer.exception.error.CustomNotificationTypeError.NOTIFICATION_TYPE_NOT_FOUND; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.*; +import org.websoso.WSSServer.domain.Notification; +import org.websoso.WSSServer.domain.NotificationType; +import org.websoso.WSSServer.domain.ReadNotification; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.domain.UserDevice; import org.websoso.WSSServer.domain.common.NotificationTypeGroup; -import org.websoso.WSSServer.dto.notification.*; +import org.websoso.WSSServer.dto.notification.NotificationCreateRequest; +import org.websoso.WSSServer.dto.notification.NotificationGetResponse; +import org.websoso.WSSServer.dto.notification.NotificationInfo; +import org.websoso.WSSServer.dto.notification.NotificationsGetResponse; +import org.websoso.WSSServer.dto.notification.NotificationsReadStatusGetResponse; import org.websoso.WSSServer.exception.exception.CustomNotificationException; import org.websoso.WSSServer.exception.exception.CustomNotificationTypeException; import org.websoso.WSSServer.notification.FCMService; @@ -17,16 +36,6 @@ import org.websoso.WSSServer.repository.ReadNotificationRepository; import org.websoso.WSSServer.repository.UserRepository; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.FEED; -import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.NOTICE; -import static org.websoso.WSSServer.domain.common.Role.ADMIN; -import static org.websoso.WSSServer.exception.error.CustomNotificationError.*; -import static org.websoso.WSSServer.exception.error.CustomNotificationTypeError.NOTIFICATION_TYPE_NOT_FOUND; - @Service @RequiredArgsConstructor @Transactional @@ -114,9 +123,6 @@ private void checkIfNotificationAlreadyRead(User user, Notification notification } public void createNoticeNotification(User user, NotificationCreateRequest request) { - validateAdminPrivilege(user); - validateNoticeType(request.notificationTypeName()); - Notification notification = notificationRepository.save(Notification.create( request.notificationTitle(), request.notificationBody(), @@ -129,20 +135,6 @@ public void createNoticeNotification(User user, NotificationCreateRequest reques sendNoticePushMessage(request.userId(), notification); } - private void validateAdminPrivilege(User user) { - if (user.getRole() != ADMIN) { - throw new CustomNotificationException(NOTIFICATION_ADMIN_ONLY, - "User who tried to create, modify, or delete the notice is not an ADMIN."); - } - } - - private void validateNoticeType(String notificationTypeName) { - if (!NotificationTypeGroup.isTypeInGroup(notificationTypeName, NOTICE)) { - throw new CustomNotificationException(NOTIFICATION_NOT_NOTICE_TYPE, - "given notification type does not belong to the NOTICE category"); - } - } - private NotificationType getNotificationTypeOrException(String notificationTypeName) { return notificationTypeRepository .findOptionalByNotificationTypeName(notificationTypeName) diff --git a/src/main/java/org/websoso/WSSServer/service/NovelService.java b/src/main/java/org/websoso/WSSServer/service/NovelService.java index 3d4e1de75..c25eab95a 100644 --- a/src/main/java/org/websoso/WSSServer/service/NovelService.java +++ b/src/main/java/org/websoso/WSSServer/service/NovelService.java @@ -144,8 +144,7 @@ public void registerAsInterest(User user, Long novelId) { } public void unregisterAsInterest(User user, Long novelId) { - Novel novel = getNovelOrException(novelId); - UserNovel userNovel = userNovelService.getUserNovelOrException(user, novel); + UserNovel userNovel = userNovelService.getUserNovelOrException(user, novelId); if (!userNovel.getIsInterest()) { throw new CustomUserNovelException(NOT_INTERESTED, "not registered as interest"); diff --git a/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java b/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java index 648ce0d12..73b57009c 100644 --- a/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java +++ b/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java @@ -3,7 +3,9 @@ import static java.lang.Boolean.TRUE; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.domain.Feed; @@ -103,16 +105,19 @@ private String generateNotificationBodyFragment(Feed feed) { @Transactional(readOnly = true) public PopularFeedsGetResponse getPopularFeeds(User user) { - List popularFeeds = findPopularFeeds(user); + List popularFeeds = Optional.ofNullable(user) + .map(u -> findPopularFeedsWithUser(u.getUserId())) + .orElseGet(this::findPopularFeedsWithoutUser); List popularFeedGetResponses = mapToPopularFeedGetResponseList(popularFeeds); return new PopularFeedsGetResponse(popularFeedGetResponses); } - private List findPopularFeeds(User user) { - if (user == null) { - return popularFeedRepository.findTop9ByOrderByPopularFeedIdDesc(); - } - return popularFeedRepository.findTodayPopularFeeds(user.getUserId()); + private List findPopularFeedsWithUser(Long userId) { + return popularFeedRepository.findTodayPopularFeeds(userId); + } + + private List findPopularFeedsWithoutUser() { + return popularFeedRepository.findTop9ByOrderByPopularFeedIdDesc(); } private static List mapToPopularFeedGetResponseList(List popularFeeds) { diff --git a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java index 1433c4bf5..3371eee95 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java +++ b/src/main/java/org/websoso/WSSServer/service/UserNovelService.java @@ -71,9 +71,9 @@ public class UserNovelService { ); @Transactional(readOnly = true) - public UserNovel getUserNovelOrException(User user, Novel novel) { - return userNovelRepository.findByNovelAndUser(novel, user).orElseThrow( - () -> new CustomUserNovelException(USER_NOVEL_NOT_FOUND, + public UserNovel getUserNovelOrException(User user, Long novelId) { + return userNovelRepository.findByNovel_NovelIdAndUser(novelId, user) + .orElseThrow(() -> new CustomUserNovelException(USER_NOVEL_NOT_FOUND, "user novel with the given user and novel is not found")); } @@ -105,9 +105,8 @@ public void createEvaluation(User user, UserNovelCreateRequest request) { createNovelKeywords(userNovel, request.keywordIds()); } - public void updateEvaluation(User user, Novel novel, UserNovelUpdateRequest request) { - UserNovel userNovel = getUserNovelOrException(user, novel); - + public void updateEvaluation(User user, Long novelId, UserNovelUpdateRequest request) { + UserNovel userNovel = getUserNovelOrException(user, novelId); updateUserNovel(userNovel, request); updateAssociations(userNovel, request); } @@ -178,8 +177,8 @@ private void createNovelKeywords(UserNovel userNovel, List request) { } } - public void deleteEvaluation(User user, Novel novel) { - UserNovel userNovel = getUserNovelOrException(user, novel); + public void deleteEvaluation(User user, Long novelId) { + UserNovel userNovel = getUserNovelOrException(user, novelId); if (userNovel.getStatus() == null) { throw new CustomUserNovelException(NOT_EVALUATED, "this novel has not been evaluated by the user"); diff --git a/src/main/java/org/websoso/WSSServer/service/UserService.java b/src/main/java/org/websoso/WSSServer/service/UserService.java index c2721a19b..23da7345d 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserService.java +++ b/src/main/java/org/websoso/WSSServer/service/UserService.java @@ -18,8 +18,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; import org.websoso.WSSServer.config.jwt.JwtProvider; -import org.websoso.WSSServer.config.jwt.UserAuthentication; import org.websoso.WSSServer.domain.Avatar; import org.websoso.WSSServer.domain.Genre; import org.websoso.WSSServer.domain.GenrePreference; @@ -28,6 +28,7 @@ import org.websoso.WSSServer.domain.WithdrawalReason; import org.websoso.WSSServer.domain.common.DiscordWebhookMessage; import org.websoso.WSSServer.domain.common.SocialLoginType; +import org.websoso.WSSServer.dto.auth.LogoutRequest; import org.websoso.WSSServer.dto.notification.PushSettingGetResponse; import org.websoso.WSSServer.dto.user.EditMyInfoRequest; import org.websoso.WSSServer.dto.user.EditProfileStatusRequest; @@ -95,8 +96,8 @@ public NicknameValidation isNicknameAvailable(User user, String nickname) { public LoginResponse login(Long userId) { User user = getUserOrException(userId); - UserAuthentication userAuthentication = new UserAuthentication(user.getUserId(), null, null); - String token = jwtProvider.generateAccessToken(userAuthentication); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, null); + String token = jwtProvider.generateAccessToken(customAuthenticationToken); return LoginResponse.of(token); } @@ -120,8 +121,8 @@ public void editProfileStatus(User user, EditProfileStatusRequest editProfileSta @Transactional(readOnly = true) public User getUserOrException(Long userId) { - return userRepository.findById(userId).orElseThrow(() -> - new CustomUserException(USER_NOT_FOUND, "user with the given id was not found")); + return userRepository.findById(userId) + .orElseThrow(() -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found")); } @Transactional(readOnly = true) @@ -182,9 +183,12 @@ public void registerUserInfo(User user, RegisterUserInfoRequest registerUserInfo MessageFormatter.formatUserJoinMessage(user, SocialLoginType.fromSocialId(user.getSocialId())), JOIN)); } - public void logout(User user, String refreshToken, String deviceIdentifier) { - refreshTokenRepository.findByRefreshToken(refreshToken).ifPresent(refreshTokenRepository::delete); - userDeviceRepository.deleteByUserAndDeviceIdentifier(user, deviceIdentifier); + public void logout(User user, LogoutRequest request) { + refreshTokenRepository.findByRefreshToken(request.refreshToken()) + .ifPresent(refreshTokenRepository::delete); + + userDeviceRepository.deleteByUserAndDeviceIdentifier(user, request.deviceIdentifier()); + if (user.getSocialId().startsWith(KAKAO_PREFIX)) { kakaoService.kakaoLogout(user); } diff --git a/src/main/java/org/websoso/WSSServer/validation/NotificationTypeConstraint.java b/src/main/java/org/websoso/WSSServer/validation/NotificationTypeConstraint.java new file mode 100644 index 000000000..e35891351 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/validation/NotificationTypeConstraint.java @@ -0,0 +1,23 @@ +package org.websoso.WSSServer.validation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = NotificationTypeValidator.class) +@Target({FIELD}) +@Retention(RUNTIME) +public @interface NotificationTypeConstraint { + + String message() default "invalid notificationType"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/org/websoso/WSSServer/validation/NotificationTypeValidator.java b/src/main/java/org/websoso/WSSServer/validation/NotificationTypeValidator.java new file mode 100644 index 000000000..de6a32a3f --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/validation/NotificationTypeValidator.java @@ -0,0 +1,29 @@ +package org.websoso.WSSServer.validation; + +import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.NOTICE; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_NOT_NOTICE_TYPE; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.websoso.WSSServer.domain.common.NotificationTypeGroup; +import org.websoso.WSSServer.exception.exception.CustomNotificationException; + +@Component +@RequiredArgsConstructor +public class NotificationTypeValidator implements ConstraintValidator { + + @Override + public void initialize(NotificationTypeConstraint notificationTypeName) { + } + + @Override + public boolean isValid(String notificationTypeName, ConstraintValidatorContext constraintValidatorContext) { + if (!NotificationTypeGroup.isTypeInGroup(notificationTypeName, NOTICE)) { + throw new CustomNotificationException(NOTIFICATION_NOT_NOTICE_TYPE, + "given notification type does not belong to the NOTICE category"); + } + return true; + } +}