Introduce Chat API with message handling, server state updates, and IP-based security restrictions.
This commit is contained in:
@@ -4,9 +4,11 @@ import com.alttd.altitudeweb.setup.Connection;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"com.alttd.altitudeweb"})
|
||||
@EnableAspectJAutoProxy
|
||||
@EnableScheduling
|
||||
public class AltitudeWebApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -26,6 +27,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
@@ -42,17 +44,24 @@ public class SecurityConfig {
|
||||
private final KeyPairService keyPairService;
|
||||
private final SecurityAuthFailureHandler securityAuthFailureHandler;
|
||||
|
||||
@Value("${chat.allowed-ip}")
|
||||
private String allowedIp;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
WebExpressionAuthorizationManager allowedIpCheck = new WebExpressionAuthorizationManager(
|
||||
"hasIpAddress('%s')".formatted(allowedIp));
|
||||
return http
|
||||
.authorizeHttpRequests(
|
||||
auth -> auth
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.requestMatchers("/api/chat/send/**").access(allowedIpCheck)
|
||||
.requestMatchers("/api/form/**").authenticated()
|
||||
.requestMatchers("/api/login/getUsername").authenticated()
|
||||
.requestMatchers("/api/mail/**").authenticated()
|
||||
.requestMatchers("/api/site/vote").authenticated()
|
||||
.requestMatchers("/api/appeal").authenticated()
|
||||
.requestMatchers("/api/chat/read/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||
.requestMatchers("/api/site/get-staff-playtime/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||
.requestMatchers("/api/head_mod/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||
.requestMatchers("/api/particles/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.alttd.altitudeweb.controllers.chat;
|
||||
|
||||
import com.alttd.altitudeweb.api.ChatApi;
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import com.alttd.altitudeweb.model.ServerStateDto;
|
||||
import com.alttd.altitudeweb.services.chat.ChatService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class ChatController implements ChatApi {
|
||||
|
||||
private final ChatService chatService;
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> sendChatMessages(List<ChatMessageDto> chatMessageDtoList) {
|
||||
//TODO [Admin] [2026-07-18]: Implement handling chat messages (push to listeners)
|
||||
chatMessageDtoList.stream().map(ChatMessageMapper::fromDto).forEach(chatService::addChatMessage);
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> updateServerStates(ServerStateDto serverStateDto) {
|
||||
//TODO [Stijn] [2026-07-18]: Implement handling server state updates (push to listeners)
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.alttd.altitudeweb.controllers.chat;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
public class ChatMessage {
|
||||
|
||||
private final UUID uuid;
|
||||
private final Instant timestamp;
|
||||
private final String server;
|
||||
//TODO [Stijn] [2026-07-18]: Handle channel types
|
||||
//private final String channel;
|
||||
private final String messageJson;
|
||||
private final boolean blocked;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alttd.altitudeweb.controllers.chat;
|
||||
|
||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ChatMessageMapper {
|
||||
|
||||
public ChatMessage fromDto(ChatMessageDto dto) {
|
||||
return ChatMessage.builder()
|
||||
.uuid(dto.getUuid())
|
||||
.timestamp(dto.getTimestamp().toInstant())//TODO [Stijn] [2026-07-18]: Check this works, might be sending instant instead
|
||||
.server(dto.getServer())
|
||||
.messageJson(dto.getMessage())
|
||||
.blocked(dto.getBlocked())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -12,7 +12,6 @@ import com.alttd.altitudeweb.model.PunishmentHistoryDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.alttd.altitudeweb.services.chat;
|
||||
|
||||
import com.alttd.altitudeweb.controllers.chat.ChatMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ChatService {
|
||||
|
||||
private static final Duration MAX_AGE = Duration.ofHours(1);
|
||||
private final NavigableMap<Instant, ChatMessage> chatMessages = new TreeMap<>();
|
||||
|
||||
@Scheduled(cron = "0 * * * * *")
|
||||
public void clearOldMessages() {
|
||||
Instant cutoff = Instant.now().minus(MAX_AGE);
|
||||
int currentSize = chatMessages.size();
|
||||
|
||||
chatMessages.headMap(cutoff, false).clear();
|
||||
|
||||
log.info("Clearing old chat messages. Removed {}/{} messages",
|
||||
currentSize - chatMessages.size(), currentSize);
|
||||
}
|
||||
|
||||
public void addChatMessage(ChatMessage chatMessage) {
|
||||
chatMessages.put(chatMessage.getTimestamp(), chatMessage);
|
||||
}
|
||||
|
||||
public List<ChatMessage> getMessagesSince(Instant instant) {
|
||||
return chatMessages.tailMap(instant, false).values().stream().toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,3 +18,4 @@ spring.mail.password=${MAIL_PASSWORD}
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.ssl.enable=true
|
||||
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
|
||||
chat.allowed-ip=${ALLOWED_IP:10.0.0.0/24}
|
||||
|
||||
Reference in New Issue
Block a user