diff --git a/src/main/java/com/krylov/refound/config/WebSocketConfig.java b/src/main/java/com/krylov/refound/config/WebSocketConfig.java index 8a3c03d..494fb33 100644 --- a/src/main/java/com/krylov/refound/config/WebSocketConfig.java +++ b/src/main/java/com/krylov/refound/config/WebSocketConfig.java @@ -1,8 +1,9 @@ package com.krylov.refound.config; -import com.krylov.refound.util.WsHandshakeInterceptor; +import com.krylov.refound.util.WsStompInterceptor; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @@ -13,19 +14,23 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo @RequiredArgsConstructor public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private final WsHandshakeInterceptor wsHandshakeInterceptor; + private final WsStompInterceptor wsStompInterceptor; @Override public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/topic"); // сервер → клиент - registry.setApplicationDestinationPrefixes("/app"); // клиент → сервер + registry.enableSimpleBroker("/topic"); + registry.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOriginPatterns("*") - .addInterceptors(wsHandshakeInterceptor) .withSockJS(); } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(wsStompInterceptor); + } } diff --git a/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java b/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java index 37aecc8..723958c 100644 --- a/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java +++ b/src/main/java/com/krylov/refound/controller/ChatWebSocketController.java @@ -2,6 +2,7 @@ package com.krylov.refound.controller; import com.krylov.refound.dto.SendMessageRequest; import com.krylov.refound.service.ChatService; +import java.util.Map; import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,25 +15,38 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor public class ChatWebSocketController { + private final ChatService chatService; private final SimpMessagingTemplate messagingTemplate; // Клиент шлёт сюда: /app/chat.send @MessageMapping("/chat.send") public void sendMessage(SendMessageRequest request, SimpMessageHeaderAccessor headerAccessor) { - Long senderId = (Long) Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("userId"); - if (senderId == null) { - log.warn("sendMessage: senderId is null, session attrs missing userId"); - return; // или отправить ошибку клиенту + Map sessionAttributes = headerAccessor.getSessionAttributes(); + + if (sessionAttributes == null) { + log.warn("sendMessage: sessionAttributes is null"); + return; } + + Object userIdObject = sessionAttributes.get("userId"); + + if (userIdObject == null) { + log.warn("sendMessage: userId missing in session attributes"); + return; + } + + Long senderId = ((Number) userIdObject).longValue(); + if (request.getContent() == null || request.getContent().isBlank()) { return; } + try { chatService.sendMessage(senderId, request.getChatId(), request.getContent()); } catch (SecurityException | IllegalArgumentException e) { - messagingTemplate.convertAndSendToUser(senderId.toString(), "/queue/errors", e.getMessage()); + messagingTemplate.convertAndSendToUser(senderId.toString(), "/queue/errors", e.getMessage() + ); } - // ChatService сам отправит сообщение всем подписчикам через messagingTemplate } } diff --git a/src/main/java/com/krylov/refound/controller/PostController.java b/src/main/java/com/krylov/refound/controller/PostController.java index b083ff3..7ec182d 100644 --- a/src/main/java/com/krylov/refound/controller/PostController.java +++ b/src/main/java/com/krylov/refound/controller/PostController.java @@ -54,10 +54,10 @@ public class PostController { @PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public PostResponse update(@PathVariable Long id, @RequestParam String type, @RequestParam String title, @RequestParam(required = false) String address, @RequestParam(required = false) Double latitude, @RequestParam(required = false) Double longitude, @RequestParam(required = false) String description, - @RequestParam(required = false) String category, @RequestParam(required = false) String phone, + @RequestParam(required = false) String category, @RequestParam(required = false) String phone, @RequestParam(required = false) Boolean reward, @RequestParam(value = "images", required = false) List images, @RequestParam(value = "existingImages", required = false) String existingImagesJson) { log.info("update post {}", id); - return service.update(id, type, title, address, latitude, longitude, description, category, phone, images, existingImagesJson); + return service.update(id, type, title, address, latitude, longitude, description, category, phone, reward, images, existingImagesJson); } @DeleteMapping("/{id}") diff --git a/src/main/java/com/krylov/refound/dto/PostRequest.java b/src/main/java/com/krylov/refound/dto/PostRequest.java index da254a1..f02ad55 100644 --- a/src/main/java/com/krylov/refound/dto/PostRequest.java +++ b/src/main/java/com/krylov/refound/dto/PostRequest.java @@ -19,4 +19,5 @@ public class PostRequest { private Double longitude; private String address; private String rulesAccepted; + private Boolean reward; } diff --git a/src/main/java/com/krylov/refound/dto/PostResponse.java b/src/main/java/com/krylov/refound/dto/PostResponse.java index cfed066..38f3efc 100644 --- a/src/main/java/com/krylov/refound/dto/PostResponse.java +++ b/src/main/java/com/krylov/refound/dto/PostResponse.java @@ -27,4 +27,5 @@ public class PostResponse { private Long likeCount; private Role userRole; private String rulesAccepted; + private Boolean reward; } diff --git a/src/main/java/com/krylov/refound/entity/Post.java b/src/main/java/com/krylov/refound/entity/Post.java index a4ad732..6ed2ed0 100644 --- a/src/main/java/com/krylov/refound/entity/Post.java +++ b/src/main/java/com/krylov/refound/entity/Post.java @@ -42,6 +42,7 @@ public class Post { private LocalDateTime createdAt; private String phone; private String rulesAccepted; + private Boolean isReward; @Enumerated(EnumType.STRING) private PostType type; diff --git a/src/main/java/com/krylov/refound/service/PostService.java b/src/main/java/com/krylov/refound/service/PostService.java index aee726c..efb944f 100644 --- a/src/main/java/com/krylov/refound/service/PostService.java +++ b/src/main/java/com/krylov/refound/service/PostService.java @@ -64,6 +64,7 @@ public class PostService { post.setCreatedAt(LocalDateTime.now()); post.setStatus(PostStatus.MODERATION); post.setUser(currentUser); + post.setIsReward(request.getReward()); if (request.getLatitude() == null || request.getLongitude() == null) { post.setCity(request.getAddress()); } else { @@ -134,7 +135,7 @@ public class PostService { */ @Transactional public PostResponse update(Long id, String type, String title, String address, Double latitude, - Double longitude, String description, String category, String phone, List images, String existingImagesJson) { + Double longitude, String description, String category, String phone, Boolean reward, List images, String existingImagesJson) { PostRequest request = new PostRequest(); request.setType(PostType.from(type)); @@ -145,6 +146,7 @@ public class PostService { request.setLatitude(latitude); request.setLongitude(longitude); request.setAddress(address); + request.setReward(reward); List existingUrls = parseExistingImages(existingImagesJson); @@ -163,6 +165,7 @@ public class PostService { } post.setPhone(request.getPhone()); post.setType(request.getType()); + post.setIsReward(request.getReward()); if (request.getLatitude() != null && request.getLongitude() != null) { post.setLatitude(request.getLatitude()); @@ -361,6 +364,7 @@ public class PostService { PostResponse response = mapper.toResponse(post); response.setLikeCount(likeCounts.getOrDefault(post.getId(), 0L)); response.setFavorite(userFavoriteIds.contains(post.getId())); + response.setReward(post.getIsReward()); if (includeUserRole) { response.setUserRole(post.getUser().getRole()); } diff --git a/src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java b/src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java deleted file mode 100644 index 58aae13..0000000 --- a/src/main/java/com/krylov/refound/util/WsHandshakeInterceptor.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.krylov.refound.util; - -import com.krylov.refound.security.JwtService; -import com.krylov.refound.service.UserService; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.HandshakeInterceptor; - -@Component -@RequiredArgsConstructor -public class WsHandshakeInterceptor implements HandshakeInterceptor { - - private final JwtService jwtService; - private final UserService userService; - - @Override - public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { - if (request instanceof ServletServerHttpRequest servletRequest) { - String token = servletRequest.getServletRequest().getParameter("token"); - if (token != null && jwtService.isTokenValid(token)) { - String login = jwtService.extractLogin(token); - Long userId = userService.getUserIdByLogin(login); - attributes.put("userId", userId); - return true; - } - } - return false; // отклонить подключение без валидного токена - } - - @Override - public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {} -} diff --git a/src/main/java/com/krylov/refound/util/WsStompInterceptor.java b/src/main/java/com/krylov/refound/util/WsStompInterceptor.java new file mode 100644 index 0000000..0ed63f2 --- /dev/null +++ b/src/main/java/com/krylov/refound/util/WsStompInterceptor.java @@ -0,0 +1,53 @@ +package com.krylov.refound.util; + +import com.krylov.refound.security.JwtService; +import com.krylov.refound.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class WsStompInterceptor implements ChannelInterceptor { + + private final JwtService jwtService; + private final UserService userService; + + @Override + public Message preSend(Message message, MessageChannel channel) { + + StompHeaderAccessor accessor = + StompHeaderAccessor.wrap(message); + + if (StompCommand.CONNECT.equals(accessor.getCommand())) { + + String authorization = accessor.getFirstNativeHeader("Authorization"); + + if (authorization == null || !authorization.startsWith("Bearer ")) { + throw new IllegalArgumentException("Missing Authorization header"); + } + + String token = authorization.substring(7); + + if (!jwtService.isTokenValid(token)) { + throw new IllegalArgumentException("Invalid JWT token"); + } + + String login = jwtService.extractLogin(token); + + Long userId = userService.getUserIdByLogin(login); + + if (userId == null) { + throw new IllegalArgumentException("User not found"); + } + + accessor.getSessionAttributes().put("userId", userId); + } + + return message; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1c3139c..bea3f1e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -54,4 +54,4 @@ jwt: app: cors: - allowed-origins: ${CORS_ALLOWED_ORIGINS:https://refound.example.com,http://localhost:3000} \ No newline at end of file + allowed-origins: http://localhost:5173,http://localhost:3000 \ No newline at end of file diff --git a/src/main/resources/db/changelog/add/014-add-reward-posts.yaml b/src/main/resources/db/changelog/add/014-add-reward-posts.yaml new file mode 100644 index 0000000..fef2c9d --- /dev/null +++ b/src/main/resources/db/changelog/add/014-add-reward-posts.yaml @@ -0,0 +1,12 @@ +databaseChangeLog: + - changeSet: + id: 014-add-reward-posts + author: alexandr krylov + changes: + - addColumn: + tableName: posts + columns: + - column: + name: is_reward + type: boolean + defaultValueBoolean: false \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 0659f3e..0815300 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -18,3 +18,4 @@ databaseChangeLog: - include: { file: db/changelog/add/013-add-rules_accepted-posts.yaml} - include: { file: db/changelog/create/009-create-statistic.yaml } - include: { file: db/changelog/alter/011-alter-posts-district-length.yaml} + - include: { file: db/changelog/add/014-add-reward-posts.yaml } \ No newline at end of file