Add chat input functionality in ChatBoxComponent and backend support for sending server messages
This commit is contained in:
parent
8533cfa365
commit
065db06e16
|
|
@ -71,6 +71,7 @@ public class SecurityConfig {
|
||||||
.requestMatchers("/api/files/download/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
.requestMatchers("/api/files/download/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.requestMatchers("/api/history/admin/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
.requestMatchers("/api/history/admin/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.requestMatchers("/api/login/userLogin/**").permitAll()
|
.requestMatchers("/api/login/userLogin/**").permitAll()
|
||||||
|
.requestMatchers("/api/site/chat/**").authenticated()
|
||||||
.requestMatchers("/api/chat/server/send/**").permitAll()
|
.requestMatchers("/api/chat/server/send/**").permitAll()
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,3 +12,15 @@
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
</div>
|
</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>
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-container {
|
.message-container {
|
||||||
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
height: 100%;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
|
@ -41,3 +43,51 @@
|
||||||
.date-time {
|
.date-time {
|
||||||
color: gray;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,63 @@
|
||||||
import {Component, input} from '@angular/core';
|
import {Component, computed, inject, input} from '@angular/core';
|
||||||
import {DatePipe} from '@angular/common';
|
import {DatePipe} from '@angular/common';
|
||||||
import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object';
|
import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object';
|
||||||
import {MiniMessageComponent} from '@pages/altitude/chat/components/mini-message/mini-message.component';
|
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({
|
@Component({
|
||||||
selector: 'app-chat-box',
|
selector: 'app-chat-box',
|
||||||
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
MiniMessageComponent,
|
MiniMessageComponent,
|
||||||
DatePipe
|
DatePipe,
|
||||||
|
FormsModule
|
||||||
],
|
],
|
||||||
templateUrl: './chat-box.component.html',
|
templateUrl: './chat-box.component.html',
|
||||||
styleUrl: './chat-box.component.scss'
|
styleUrl: './chat-box.component.scss'
|
||||||
})
|
})
|
||||||
export class ChatBoxComponent {
|
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[]>();
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ tags:
|
||||||
description: Actions related to small features on the site such as displaying vote stats or pt/rank stats
|
description: Actions related to small features on the site such as displaying vote stats or pt/rank stats
|
||||||
- name: chatInfo
|
- name: chatInfo
|
||||||
description: All actions related to chat information
|
description: All actions related to chat information
|
||||||
|
- name: server-message
|
||||||
|
description: All actions related to sending messages to servers
|
||||||
paths:
|
paths:
|
||||||
/api/team/{team}:
|
/api/team/{team}:
|
||||||
$ref: './schemas/team/team.yml#/getTeam'
|
$ref: './schemas/team/team.yml#/getTeam'
|
||||||
|
|
@ -103,3 +105,5 @@ paths:
|
||||||
$ref: './schemas/chat_info/chat_info.yml#/PartyName'
|
$ref: './schemas/chat_info/chat_info.yml#/PartyName'
|
||||||
/api/site/chat/player/{uuid}/name:
|
/api/site/chat/player/{uuid}/name:
|
||||||
$ref: './schemas/chat_info/chat_info.yml#/UserName'
|
$ref: './schemas/chat_info/chat_info.yml#/UserName'
|
||||||
|
/api/site/chat/send-message/{server}:
|
||||||
|
$ref: './schemas/server-message/server-message.yml#/sendServerMessage'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user