Merge pull request #26 from SlimusMinus/fix-web_socket
fixed web-socket and add rewards on post
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
package com.krylov.refound.config;
|
package com.krylov.refound.config;
|
||||||
|
|
||||||
import com.krylov.refound.util.WsHandshakeInterceptor;
|
import com.krylov.refound.util.WsStompInterceptor;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.messaging.simp.config.ChannelRegistration;
|
||||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||||
@@ -13,19 +14,23 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||||
|
|
||||||
private final WsHandshakeInterceptor wsHandshakeInterceptor;
|
private final WsStompInterceptor wsStompInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||||
registry.enableSimpleBroker("/topic"); // сервер → клиент
|
registry.enableSimpleBroker("/topic");
|
||||||
registry.setApplicationDestinationPrefixes("/app"); // клиент → сервер
|
registry.setApplicationDestinationPrefixes("/app");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||||
registry.addEndpoint("/ws")
|
registry.addEndpoint("/ws")
|
||||||
.setAllowedOriginPatterns("*")
|
.setAllowedOriginPatterns("*")
|
||||||
.addInterceptors(wsHandshakeInterceptor)
|
|
||||||
.withSockJS();
|
.withSockJS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||||
|
registration.interceptors(wsStompInterceptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.krylov.refound.controller;
|
|||||||
|
|
||||||
import com.krylov.refound.dto.SendMessageRequest;
|
import com.krylov.refound.dto.SendMessageRequest;
|
||||||
import com.krylov.refound.service.ChatService;
|
import com.krylov.refound.service.ChatService;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -14,25 +15,38 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ChatWebSocketController {
|
public class ChatWebSocketController {
|
||||||
|
|
||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
private final SimpMessagingTemplate messagingTemplate;
|
private final SimpMessagingTemplate messagingTemplate;
|
||||||
|
|
||||||
// Клиент шлёт сюда: /app/chat.send
|
// Клиент шлёт сюда: /app/chat.send
|
||||||
@MessageMapping("/chat.send")
|
@MessageMapping("/chat.send")
|
||||||
public void sendMessage(SendMessageRequest request, SimpMessageHeaderAccessor headerAccessor) {
|
public void sendMessage(SendMessageRequest request, SimpMessageHeaderAccessor headerAccessor) {
|
||||||
Long senderId = (Long) Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("userId");
|
Map<String, Object> sessionAttributes = headerAccessor.getSessionAttributes();
|
||||||
if (senderId == null) {
|
|
||||||
log.warn("sendMessage: senderId is null, session attrs missing userId");
|
if (sessionAttributes == null) {
|
||||||
return; // или отправить ошибку клиенту
|
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()) {
|
if (request.getContent() == null || request.getContent().isBlank()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
chatService.sendMessage(senderId, request.getChatId(), request.getContent());
|
chatService.sendMessage(senderId, request.getChatId(), request.getContent());
|
||||||
} catch (SecurityException | IllegalArgumentException e) {
|
} catch (SecurityException | IllegalArgumentException e) {
|
||||||
messagingTemplate.convertAndSendToUser(senderId.toString(), "/queue/errors", e.getMessage());
|
messagingTemplate.convertAndSendToUser(senderId.toString(), "/queue/errors", e.getMessage()
|
||||||
}
|
);
|
||||||
// ChatService сам отправит сообщение всем подписчикам через messagingTemplate
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ public class PostController {
|
|||||||
@PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@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,
|
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) 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<MultipartFile> images, @RequestParam(value = "existingImages", required = false) String existingImagesJson) {
|
@RequestParam(value = "images", required = false) List<MultipartFile> images, @RequestParam(value = "existingImages", required = false) String existingImagesJson) {
|
||||||
log.info("update post {}", id);
|
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}")
|
@DeleteMapping("/{id}")
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ public class PostRequest {
|
|||||||
private Double longitude;
|
private Double longitude;
|
||||||
private String address;
|
private String address;
|
||||||
private String rulesAccepted;
|
private String rulesAccepted;
|
||||||
|
private Boolean reward;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ public class PostResponse {
|
|||||||
private Long likeCount;
|
private Long likeCount;
|
||||||
private Role userRole;
|
private Role userRole;
|
||||||
private String rulesAccepted;
|
private String rulesAccepted;
|
||||||
|
private Boolean reward;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class Post {
|
|||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private String phone;
|
private String phone;
|
||||||
private String rulesAccepted;
|
private String rulesAccepted;
|
||||||
|
private Boolean isReward;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private PostType type;
|
private PostType type;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public class PostService {
|
|||||||
post.setCreatedAt(LocalDateTime.now());
|
post.setCreatedAt(LocalDateTime.now());
|
||||||
post.setStatus(PostStatus.MODERATION);
|
post.setStatus(PostStatus.MODERATION);
|
||||||
post.setUser(currentUser);
|
post.setUser(currentUser);
|
||||||
|
post.setIsReward(request.getReward());
|
||||||
if (request.getLatitude() == null || request.getLongitude() == null) {
|
if (request.getLatitude() == null || request.getLongitude() == null) {
|
||||||
post.setCity(request.getAddress());
|
post.setCity(request.getAddress());
|
||||||
} else {
|
} else {
|
||||||
@@ -134,7 +135,7 @@ public class PostService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public PostResponse update(Long id, String type, String title, String address, Double latitude,
|
public PostResponse update(Long id, String type, String title, String address, Double latitude,
|
||||||
Double longitude, String description, String category, String phone, List<MultipartFile> images, String existingImagesJson) {
|
Double longitude, String description, String category, String phone, Boolean reward, List<MultipartFile> images, String existingImagesJson) {
|
||||||
|
|
||||||
PostRequest request = new PostRequest();
|
PostRequest request = new PostRequest();
|
||||||
request.setType(PostType.from(type));
|
request.setType(PostType.from(type));
|
||||||
@@ -145,6 +146,7 @@ public class PostService {
|
|||||||
request.setLatitude(latitude);
|
request.setLatitude(latitude);
|
||||||
request.setLongitude(longitude);
|
request.setLongitude(longitude);
|
||||||
request.setAddress(address);
|
request.setAddress(address);
|
||||||
|
request.setReward(reward);
|
||||||
|
|
||||||
List<String> existingUrls = parseExistingImages(existingImagesJson);
|
List<String> existingUrls = parseExistingImages(existingImagesJson);
|
||||||
|
|
||||||
@@ -163,6 +165,7 @@ public class PostService {
|
|||||||
}
|
}
|
||||||
post.setPhone(request.getPhone());
|
post.setPhone(request.getPhone());
|
||||||
post.setType(request.getType());
|
post.setType(request.getType());
|
||||||
|
post.setIsReward(request.getReward());
|
||||||
|
|
||||||
if (request.getLatitude() != null && request.getLongitude() != null) {
|
if (request.getLatitude() != null && request.getLongitude() != null) {
|
||||||
post.setLatitude(request.getLatitude());
|
post.setLatitude(request.getLatitude());
|
||||||
@@ -361,6 +364,7 @@ public class PostService {
|
|||||||
PostResponse response = mapper.toResponse(post);
|
PostResponse response = mapper.toResponse(post);
|
||||||
response.setLikeCount(likeCounts.getOrDefault(post.getId(), 0L));
|
response.setLikeCount(likeCounts.getOrDefault(post.getId(), 0L));
|
||||||
response.setFavorite(userFavoriteIds.contains(post.getId()));
|
response.setFavorite(userFavoriteIds.contains(post.getId()));
|
||||||
|
response.setReward(post.getIsReward());
|
||||||
if (includeUserRole) {
|
if (includeUserRole) {
|
||||||
response.setUserRole(post.getUser().getRole());
|
response.setUserRole(post.getUser().getRole());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<String, Object> 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) {}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,4 +54,4 @@ jwt:
|
|||||||
|
|
||||||
app:
|
app:
|
||||||
cors:
|
cors:
|
||||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:https://refound.example.com,http://localhost:3000}
|
allowed-origins: http://localhost:5173,http://localhost:3000
|
||||||
@@ -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
|
||||||
@@ -18,3 +18,4 @@ databaseChangeLog:
|
|||||||
- include: { file: db/changelog/add/013-add-rules_accepted-posts.yaml}
|
- include: { file: db/changelog/add/013-add-rules_accepted-posts.yaml}
|
||||||
- include: { file: db/changelog/create/009-create-statistic.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/alter/011-alter-posts-district-length.yaml}
|
||||||
|
- include: { file: db/changelog/add/014-add-reward-posts.yaml }
|
||||||
Reference in New Issue
Block a user