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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MultipartFile> 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}")
|
||||
|
||||
@@ -19,4 +19,5 @@ public class PostRequest {
|
||||
private Double longitude;
|
||||
private String address;
|
||||
private String rulesAccepted;
|
||||
private Boolean reward;
|
||||
}
|
||||
|
||||
@@ -27,4 +27,5 @@ public class PostResponse {
|
||||
private Long likeCount;
|
||||
private Role userRole;
|
||||
private String rulesAccepted;
|
||||
private Boolean reward;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<MultipartFile> images, String existingImagesJson) {
|
||||
Double longitude, String description, String category, String phone, Boolean reward, List<MultipartFile> 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<String> 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());
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
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/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 }
|
||||
Reference in New Issue
Block a user