Add ChatInfoService for retrieving player and party names: integrated backend APIs, updated chat service with caching logic for names, and extended OpenAPI documentation.
This commit is contained in:
parent
79a62e9ce9
commit
8ecfb99b27
|
|
@ -2,7 +2,6 @@ package com.alttd.altitudeweb.controllers.chat;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.api.ChatApi;
|
import com.alttd.altitudeweb.api.ChatApi;
|
||||||
import com.alttd.altitudeweb.model.ChatMessageDto;
|
import com.alttd.altitudeweb.model.ChatMessageDto;
|
||||||
import com.alttd.altitudeweb.model.PlayerNameDto;
|
|
||||||
import com.alttd.altitudeweb.model.ServerStateDto;
|
import com.alttd.altitudeweb.model.ServerStateDto;
|
||||||
import com.alttd.altitudeweb.services.chat.ChatService;
|
import com.alttd.altitudeweb.services.chat.ChatService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -12,7 +11,6 @@ import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
|
|
@ -33,24 +31,4 @@ public class ChatController implements ChatApi {
|
||||||
//TODO [Stijn] [2026-07-18]: Implement handling server state updates (push to listeners)
|
//TODO [Stijn] [2026-07-18]: Implement handling server state updates (push to listeners)
|
||||||
throw new UnsupportedOperationException("Not implemented");
|
throw new UnsupportedOperationException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ResponseEntity<PlayerNameDto> getName(UUID uuid) {
|
|
||||||
return chatService.getPlayerName(uuid)
|
|
||||||
.map(ResponseEntity::ok)
|
|
||||||
.orElse(ResponseEntity.notFound().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ResponseEntity<String> getPartyName(String id) {
|
|
||||||
int partyId;
|
|
||||||
try {
|
|
||||||
partyId = Integer.parseInt(id);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
return chatService.getPartyName(partyId)
|
|
||||||
.map(ResponseEntity::ok)
|
|
||||||
.orElse(ResponseEntity.notFound().build());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.alttd.altitudeweb.controllers.chat;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.api.ChatInfoApi;
|
||||||
|
import com.alttd.altitudeweb.model.PlayerNameDto;
|
||||||
|
import com.alttd.altitudeweb.services.chat.ChatInfoService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChatInfoController implements ChatInfoApi {
|
||||||
|
|
||||||
|
private final ChatInfoService chatInfoService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<PlayerNameDto> getName(UUID uuid) {
|
||||||
|
return chatInfoService.getPlayerName(uuid)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<String> getPartyName(String id) {
|
||||||
|
int partyId;
|
||||||
|
try {
|
||||||
|
partyId = Integer.parseInt(id);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
}
|
||||||
|
return chatInfoService.getPartyName(partyId)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.alttd.altitudeweb.services.chat;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.chat.NicknameMapper;
|
||||||
|
import com.alttd.altitudeweb.database.chat.PartyMapper;
|
||||||
|
import com.alttd.altitudeweb.database.luckperms.UUIDUsernameMapper;
|
||||||
|
import com.alttd.altitudeweb.model.PlayerNameDto;
|
||||||
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChatInfoService {
|
||||||
|
|
||||||
|
public Optional<PlayerNameDto> getPlayerName(UUID uuid) {
|
||||||
|
String username = Connection.getConnection(Databases.LUCK_PERMS)
|
||||||
|
.runQueryWithResult(sqlSession -> sqlSession.getMapper(UUIDUsernameMapper.class).getUsernameFromUUID(uuid.toString()));
|
||||||
|
|
||||||
|
if (username == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<String> nickname = Connection.getConnection(Databases.CHAT)
|
||||||
|
.runQueryWithResult(sqlSession -> sqlSession.getMapper(NicknameMapper.class).getNicknameFromUUID(uuid.toString()));
|
||||||
|
|
||||||
|
PlayerNameDto playerNameDto = new PlayerNameDto();
|
||||||
|
playerNameDto.setName(username);
|
||||||
|
playerNameDto.setNickname(nickname.orElse(null));
|
||||||
|
|
||||||
|
return Optional.of(playerNameDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getPartyName(int id) {
|
||||||
|
return Connection.getConnection(Databases.CHAT)
|
||||||
|
.runQueryWithResult(sqlSession -> sqlSession.getMapper(PartyMapper.class).getPartyName(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,7 @@ import com.alttd.altitudeweb.controllers.chat.ChatMessage;
|
||||||
import com.alttd.altitudeweb.controllers.chat.ChatMessageMapper;
|
import com.alttd.altitudeweb.controllers.chat.ChatMessageMapper;
|
||||||
import com.alttd.altitudeweb.database.Databases;
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
import com.alttd.altitudeweb.database.chat.ChatLogMapper;
|
import com.alttd.altitudeweb.database.chat.ChatLogMapper;
|
||||||
import com.alttd.altitudeweb.database.chat.NicknameMapper;
|
|
||||||
import com.alttd.altitudeweb.database.chat.PartyMapper;
|
|
||||||
import com.alttd.altitudeweb.database.luckperms.UUIDUsernameMapper;
|
|
||||||
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
||||||
import com.alttd.altitudeweb.model.PlayerNameDto;
|
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
|
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
|
||||||
import com.alttd.altitudeweb.setup.Connection;
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
|
|
@ -145,27 +141,4 @@ public class ChatService {
|
||||||
return chatMessages.tailMap(instant, false).values().stream().toList();
|
return chatMessages.tailMap(instant, false).values().stream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<PlayerNameDto> getPlayerName(UUID uuid) {
|
|
||||||
String username = Connection.getConnection(Databases.LUCK_PERMS)
|
|
||||||
.runQueryWithResult(sqlSession -> sqlSession.getMapper(UUIDUsernameMapper.class).getUsernameFromUUID(uuid.toString()));
|
|
||||||
|
|
||||||
if (username == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<String> nickname = Connection.getConnection(Databases.CHAT)
|
|
||||||
.runQueryWithResult(sqlSession -> sqlSession.getMapper(NicknameMapper.class).getNicknameFromUUID(uuid.toString()));
|
|
||||||
|
|
||||||
PlayerNameDto playerNameDto = new PlayerNameDto();
|
|
||||||
playerNameDto.setName(username);
|
|
||||||
playerNameDto.setNickname(nickname.orElse(null));
|
|
||||||
|
|
||||||
return Optional.of(playerNameDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getPartyName(int id) {
|
|
||||||
return Connection.getConnection(Databases.CHAT)
|
|
||||||
.runQueryWithResult(sqlSession -> sqlSession.getMapper(PartyMapper.class).getPartyName(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {RawChatMessage} from '@pages/altitude/chat/objects/raw-chat-message.obje
|
||||||
import {normalizeComponent} from '@pages/altitude/chat/mini-message/normalize.util';
|
import {normalizeComponent} from '@pages/altitude/chat/mini-message/normalize.util';
|
||||||
import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object';
|
import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object';
|
||||||
import {NotificationService} from '@pages/altitude/chat/service/chat-notification.service';
|
import {NotificationService} from '@pages/altitude/chat/service/chat-notification.service';
|
||||||
|
import {ChatInfoService} from '@api';
|
||||||
|
|
||||||
interface SsePayloadEvent {
|
interface SsePayloadEvent {
|
||||||
data: string;
|
data: string;
|
||||||
|
|
@ -16,11 +17,16 @@ export class ChatService implements OnDestroy {
|
||||||
private eventSource?: EventSourcePolyfill;
|
private eventSource?: EventSourcePolyfill;
|
||||||
private readonly notificationService: NotificationService = inject(NotificationService);
|
private readonly notificationService: NotificationService = inject(NotificationService);
|
||||||
private readonly authService: AuthService = inject(AuthService)
|
private readonly authService: AuthService = inject(AuthService)
|
||||||
|
private readonly chatInfoService: ChatInfoService = inject(ChatInfoService);
|
||||||
private readonly _messages = signal<ChatMessage[]>([])
|
private readonly _messages = signal<ChatMessage[]>([])
|
||||||
private readonly _channels = signal<ChatChannel[]>([])
|
private readonly _channels = signal<ChatChannel[]>([])
|
||||||
public readonly channels = computed(() => this._channels().sort((a, b) => a.name.localeCompare(b.name)));
|
public readonly channels = computed(() => this._channels().sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
private readonly _selectedChannel = signal<ChatChannel | null>(null);
|
private readonly _selectedChannel = signal<ChatChannel | null>(null);
|
||||||
public readonly selectedChannel = this._selectedChannel.asReadonly()
|
public readonly selectedChannel = this._selectedChannel.asReadonly()
|
||||||
|
public readonly partieMap = signal<Map<string, string>>(new Map());
|
||||||
|
public readonly userNameMap = signal<Map<string, string>>(new Map());
|
||||||
|
private readonly pendingPartyNameRequests = new Set<string>();
|
||||||
|
private readonly pendingUserNameRequests = new Set<string>();
|
||||||
|
|
||||||
public readonly messages = computed(() => {
|
public readonly messages = computed(() => {
|
||||||
const selected = this._selectedChannel();
|
const selected = this._selectedChannel();
|
||||||
|
|
@ -109,18 +115,72 @@ export class ChatService implements OnDestroy {
|
||||||
return {name: 'Global Admin Chat', type: 'GAC'};
|
return {name: 'Global Admin Chat', type: 'GAC'};
|
||||||
}
|
}
|
||||||
if (message.type === 'PARTY') {
|
if (message.type === 'PARTY') {
|
||||||
|
if (!message.channelName) {
|
||||||
|
throw new Error('Party name is missing');
|
||||||
|
}
|
||||||
|
this.updatePartyMap(message.channelName)
|
||||||
return {name: message.channel, type: 'PARTY'};
|
return {name: message.channel, type: 'PARTY'};
|
||||||
}
|
}
|
||||||
if (message.type === 'MSG') {
|
if (message.type === 'MSG') {
|
||||||
const uuid = this.authService.getUuid();
|
const uuid = this.authService.getUuid();
|
||||||
if (message.uuid === uuid) {
|
if (message.uuid === uuid) {
|
||||||
|
this.updateUserNameMap(message.receiver)
|
||||||
return {name: message.receiver, type: 'DM'};
|
return {name: message.receiver, type: 'DM'};
|
||||||
}
|
}
|
||||||
|
this.updateUserNameMap(message.uuid)
|
||||||
return {name: message.uuid, type: 'DM'};
|
return {name: message.uuid, type: 'DM'};
|
||||||
}
|
}
|
||||||
return {name: message.server, type: 'SERVER'};
|
return {name: message.server, type: 'SERVER'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updatePartyMap(partieId: string): void {
|
||||||
|
if (this.partieMap().has(partieId) || this.pendingPartyNameRequests.has(partieId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pendingPartyNameRequests.add(partieId);
|
||||||
|
|
||||||
|
this.chatInfoService.getPartyName(partieId).subscribe({
|
||||||
|
next: (partyName) => {
|
||||||
|
this.partieMap.update((old) => {
|
||||||
|
const newMap = new Map(old);
|
||||||
|
newMap.set(partieId, partyName);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(`Failed to load party name for ${partieId}:`, error);
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.pendingPartyNameRequests.delete(partieId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateUserNameMap(uuid: string): void {
|
||||||
|
if (this.userNameMap().has(uuid) || this.pendingUserNameRequests.has(uuid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pendingUserNameRequests.add(uuid);
|
||||||
|
|
||||||
|
this.chatInfoService.getName(uuid).subscribe({
|
||||||
|
next: (userName) => {
|
||||||
|
this.userNameMap.update((old) => {
|
||||||
|
const newMap = new Map(old);
|
||||||
|
newMap.set(uuid, userName.nickname ?? userName.name);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(`Failed to load username for ${uuid}:`, error);
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.pendingUserNameRequests.delete(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public selectChannel(channel: ChatChannel) {
|
public selectChannel(channel: ChatChannel) {
|
||||||
this._selectedChannel.set(channel);
|
this._selectedChannel.set(channel);
|
||||||
this._channels.update((old) =>
|
this._channels.update((old) =>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ tags:
|
||||||
description: All actions related to user email verification
|
description: All actions related to user email verification
|
||||||
- name: site
|
- name: site
|
||||||
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
|
||||||
|
description: All actions related to chat information
|
||||||
paths:
|
paths:
|
||||||
/api/team/{team}:
|
/api/team/{team}:
|
||||||
$ref: './schemas/team/team.yml#/getTeam'
|
$ref: './schemas/team/team.yml#/getTeam'
|
||||||
|
|
@ -97,3 +99,7 @@ paths:
|
||||||
$ref: './schemas/site/vote.yml#/VoteStats'
|
$ref: './schemas/site/vote.yml#/VoteStats'
|
||||||
/api/site/get-staff-playtime/{from}/{to}:
|
/api/site/get-staff-playtime/{from}/{to}:
|
||||||
$ref: './schemas/site/staff_pt.yml#/GetStaffPlaytime'
|
$ref: './schemas/site/staff_pt.yml#/GetStaffPlaytime'
|
||||||
|
/api/site/chat/party/{id}/name:
|
||||||
|
$ref: './schemas/chat_info/chat_info.yml#/PartyName'
|
||||||
|
/api/site/chat/player/{uuid}/name:
|
||||||
|
$ref: './schemas/chat_info/chat_info.yml#/UserName'
|
||||||
|
|
|
||||||
|
|
@ -45,64 +45,8 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
"202":
|
"202":
|
||||||
description: Accepted
|
description: Accepted
|
||||||
|
|
||||||
/chat/player/{uuid}/name:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- chat
|
|
||||||
summary: Gets the name, and nickname of the player associated with that uuid
|
|
||||||
operationId: getName
|
|
||||||
parameters:
|
|
||||||
- name: uuid
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The player's name and nickname
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: "#/components/schemas/PlayerName"
|
|
||||||
"404":
|
|
||||||
description: Player not found
|
|
||||||
|
|
||||||
/chat/party/{id}/name:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- chat
|
|
||||||
summary: Gets the name, of the party based on the id
|
|
||||||
operationId: getPartyName
|
|
||||||
parameters:
|
|
||||||
- name: id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: The party name
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"400":
|
|
||||||
description: Invalid ID
|
|
||||||
"404":
|
|
||||||
description: Party not found
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
PlayerName:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- name
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
nickname:
|
|
||||||
type: string
|
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|
|
||||||
57
open_api/src/main/resources/schemas/chat_info/chat_info.yml
Normal file
57
open_api/src/main/resources/schemas/chat_info/chat_info.yml
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
UserName:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- chatInfo
|
||||||
|
summary: Gets the name, and nickname of the player associated with that uuid
|
||||||
|
operationId: getName
|
||||||
|
parameters:
|
||||||
|
- name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The player's name and nickname
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PlayerName"
|
||||||
|
"404":
|
||||||
|
description: Player not found
|
||||||
|
|
||||||
|
PartyName:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- chatInfo
|
||||||
|
summary: Gets the name, of the party based on the id
|
||||||
|
operationId: getPartyName
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The party name
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Invalid ID
|
||||||
|
"404":
|
||||||
|
description: Party not found
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PlayerName:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
nickname:
|
||||||
|
type: string
|
||||||
Loading…
Reference in New Issue
Block a user