Add chat input functionality in ChatBoxComponent and backend support for sending server messages

This commit is contained in:
akastijn 2026-08-02 18:30:33 +02:00
parent 8533cfa365
commit 065db06e16
7 changed files with 207 additions and 4 deletions

View File

@ -71,6 +71,7 @@ public class SecurityConfig {
.requestMatchers("/api/files/download/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
.requestMatchers("/api/history/admin/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
.requestMatchers("/api/login/userLogin/**").permitAll()
.requestMatchers("/api/site/chat/**").authenticated()
.requestMatchers("/api/chat/server/send/**").permitAll()
.anyRequest().permitAll()
)

View File

@ -0,0 +1,46 @@
package com.alttd.altitudeweb.controllers.chat;
import com.alttd.altitudeweb.api.ServerMessageApi;
import com.alttd.altitudeweb.model.ServerMessageRequestDto;
import com.alttd.altitudeweb.services.chat.to_server.ServerMessageService;
import com.alttd.altitudeweb.services.chat.to_server.data.ChatFromWeb;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequiredArgsConstructor
public class ServerMessageController implements ServerMessageApi {
private final ServerMessageService serverMessageService;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static String toJson(Object object) {
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Failed to serialize object to JSON", e);
}
}
@Override
public ResponseEntity<Void> sendServerMessage(String server, ServerMessageRequestDto serverMessageRequestDto) {
ChatFromWeb chatFromWeb = ChatFromWeb.builder()
.sender(serverMessageRequestDto.getUuid())
.message(serverMessageRequestDto.getMessage())
.build();
String json = toJson(chatFromWeb);
if (serverMessageService.sendMessage(server, "web_chat", json)) {
return new ResponseEntity<>(HttpStatus.ACCEPTED);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}

View File

@ -12,3 +12,15 @@
</p>
}
</div>
@if (canChat()) {
<div class="chat-input-container">
<input
type="text"
[(ngModel)]="chatText"
(keyup.enter)="sendMessage()"
placeholder="Type a message..."
/>
<button (click)="sendMessage()">Send</button>
</div>
}

View File

@ -1,12 +1,14 @@
:host {
display: block;
display: flex;
flex-direction: column;
height: 100%;
}
.message-container {
flex: 1 1 auto;
display: flex;
flex-direction: column-reverse;
height: 100%;
min-height: 0;
overflow-y: auto;
box-sizing: border-box;
scrollbar-width: thin;
@ -41,3 +43,51 @@
.date-time {
color: gray;
}
.chat-input-container {
flex: 0 0 auto;
padding: 10px;
display: flex;
gap: 10px;
background: rgba(0, 0, 0, 0.2);
border-top: 1px solid rgba(255, 255, 255, 0.1);
input {
flex: 1 1 auto;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 8px 12px;
color: white;
outline: none;
font-size: 14px;
&:focus {
border-color: #4f8cff;
background: rgba(255, 255, 255, 0.1);
}
&::placeholder {
color: rgba(255, 255, 255, 0.3);
}
}
button {
cursor: pointer;
border: 0;
border-radius: 4px;
padding: 8px 16px;
background: #4f8cff;
color: #ffffff;
font-weight: 700;
transition: background 0.2s;
&:hover {
background: #3b7dff;
}
&:active {
background: #2a6ae0;
}
}
}

View File

@ -1,17 +1,63 @@
import {Component, input} from '@angular/core';
import {Component, computed, inject, input} from '@angular/core';
import {DatePipe} from '@angular/common';
import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object';
import {MiniMessageComponent} from '@pages/altitude/chat/components/mini-message/mini-message.component';
import {AuthService} from '@services/auth.service';
import {ChatService} from '@pages/altitude/chat/service/chat.service';
import {ServerMessageService} from '@api';
import {FormsModule} from '@angular/forms';
import {MatSnackBar} from '@angular/material/snack-bar';
@Component({
selector: 'app-chat-box',
standalone: true,
imports: [
MiniMessageComponent,
DatePipe
DatePipe,
FormsModule
],
templateUrl: './chat-box.component.html',
styleUrl: './chat-box.component.scss'
})
export class ChatBoxComponent {
private readonly authService = inject(AuthService);
private readonly chatService = inject(ChatService);
private readonly serverMessageService = inject(ServerMessageService);
private readonly matSnackBar = inject(MatSnackBar);
readonly messages = input.required<ChatMessage[]>();
chatText = '';
readonly canChat = computed(() => {
const hasPrivilege = this.authService.hasAccess(['SCOPE_head_mod']);
const isServerChannel = this.chatService.selectedChannel()?.type === 'SERVER';
if (!hasPrivilege || !isServerChannel) {
console.log(`User has privilege ${hasPrivilege}, user in server channel ${isServerChannel}`);
}
return hasPrivilege && isServerChannel;
});
sendMessage() {
const message = this.chatText.trim();
if (!message) return;
const selectedChannel = this.chatService.selectedChannel();
const uuid = this.authService.getUuid();
if (selectedChannel?.type === 'SERVER' && uuid) {
this.serverMessageService.sendServerMessage(selectedChannel.name, {
uuid,
message
}).subscribe({
next: () => {
this.chatText = '';
},
error: (err) => {
this.matSnackBar.open('Failed to send message', 'Dismiss', {
duration: 3000
});
}
});
}
}
}

View File

@ -32,6 +32,8 @@ tags:
description: Actions related to small features on the site such as displaying vote stats or pt/rank stats
- name: chatInfo
description: All actions related to chat information
- name: server-message
description: All actions related to sending messages to servers
paths:
/api/team/{team}:
$ref: './schemas/team/team.yml#/getTeam'
@ -103,3 +105,5 @@ paths:
$ref: './schemas/chat_info/chat_info.yml#/PartyName'
/api/site/chat/player/{uuid}/name:
$ref: './schemas/chat_info/chat_info.yml#/UserName'
/api/site/chat/send-message/{server}:
$ref: './schemas/server-message/server-message.yml#/sendServerMessage'

View File

@ -0,0 +1,44 @@
sendServerMessage:
post:
tags:
- server-message
summary: Send a message to a specific server
operationId: sendServerMessage
parameters:
- $ref: '#/components/parameters/Server'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ServerMessageRequest'
responses:
'202':
description: Message accepted and sent
'404':
description: Server not found or not connected
'400':
description: Invalid request
components:
parameters:
Server:
name: server
in: path
required: true
schema:
type: string
description: The name of the server
schemas:
ServerMessageRequest:
type: object
required:
- uuid
- message
properties:
uuid:
type: string
format: uuid
description: The sender UUID
message:
type: string
description: The message to send