Add API endpoints for search result counts by name and UUID

Introduced new API paths and backend logic to retrieve total punishment counts based on user search queries using names or UUIDs. Updated the frontend to utilize these endpoints and display the total search results dynamically.
This commit is contained in:
Teriuihi 2025-04-18 20:43:17 +02:00
parent ee66bdda83
commit ecee377f01
6 changed files with 216 additions and 16 deletions

View File

@ -111,6 +111,48 @@ public class HistoryApiController implements HistoryApi {
return mapHistoryCount(historyCountCompletableFuture.join()); return mapHistoryCount(historyCountCompletableFuture.join());
} }
@Override
public ResponseEntity<Integer> getTotalResultsForUserSearch(String userType, String type, String user) {
UserType userTypeEnum = UserType.getUserType(userType);
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
CompletableFuture<Integer> searchResultCountCompletableFuture = new CompletableFuture<>();
Connection.getConnection(Databases.LITE_BANS)
.runQuery(sqlSession -> {
log.debug("Loading name search result count");
try {
Integer punishmentCount = sqlSession.getMapper(HistoryCountMapper.class)
.getNamePunishmentCount(historyTypeEnum, userTypeEnum, user);
searchResultCountCompletableFuture.complete(punishmentCount);
} catch (Exception e) {
log.error("Failed to load history count", e);
searchResultCountCompletableFuture.completeExceptionally(e);
}
});
return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
}
@Override
public ResponseEntity<Integer> getTotalResultsForUuidSearch(String userType, String type, String uuid) {
UserType userTypeEnum = UserType.getUserType(userType);
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
CompletableFuture<Integer> searchResultCountCompletableFuture = new CompletableFuture<>();
Connection.getConnection(Databases.LITE_BANS)
.runQuery(sqlSession -> {
log.debug("Loading uuid search result count");
try {
Integer punishmentCount = sqlSession.getMapper(HistoryCountMapper.class)
.getUuidPunishmentCount(historyTypeEnum, userTypeEnum, UUID.fromString(uuid));
searchResultCountCompletableFuture.complete(punishmentCount);
} catch (Exception e) {
log.error("Failed to load history count", e);
searchResultCountCompletableFuture.completeExceptionally(e);
}
});
return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
}
private ResponseEntity<HistoryCountDto> mapHistoryCount(HistoryCount historyCount) { private ResponseEntity<HistoryCountDto> mapHistoryCount(HistoryCount historyCount) {
HistoryCountDto historyCountDto = new HistoryCountDto(); HistoryCountDto historyCountDto = new HistoryCountDto();
historyCountDto.setBans(historyCount.getBans()); historyCountDto.setBans(historyCount.getBans());

View File

@ -1,7 +1,12 @@
package com.alttd.altitudeweb.database.litebans; package com.alttd.altitudeweb.database.litebans;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import java.util.Arrays;
import java.util.UUID;
import java.util.stream.Collectors;
public interface HistoryCountMapper { public interface HistoryCountMapper {
/** /**
* Gets the total count of punishments from all LiteBans tables. * Gets the total count of punishments from all LiteBans tables.
@ -17,4 +22,76 @@ public interface HistoryCountMapper {
""" """
}) })
HistoryCount getPunishmentCounts(); HistoryCount getPunishmentCounts();
@Select("""
SELECT COUNT(*)
FROM (SELECT punishment.uuid, user_lookup.name AS punished_name
FROM all_punishments AS punishment
JOIN user_lookup ON user_lookup.uuid = punishment.uuid
WHERE user_lookup.name LIKE #{partialName}
AND type IN (${typeList})) AS punishment
""")
Integer getPlayerNamePunishmentCount(@Param("typeList") String typeList,
@Param("partialName") String partialName);
@Select("""
SELECT COUNT(*) AS punishment_count
FROM (SELECT uuid, banned_by_name
FROM all_punishments
WHERE banned_by_name LIKE #{partialName}
AND type IN (${typeList})) AS punishment
""")
Integer getStaffNamePunishmentCount(@Param("typeList") String typeList,
@Param("partialName") String partialName);
default Integer getNamePunishmentCount(HistoryType historyTypeEnum, UserType userTypeEnum, String partialName) {
final String searchName = partialName.toLowerCase().replace("_", "\\_") + "%";
String[] historyType = getHistoryType(historyTypeEnum);
String historyTypeSqlList = Arrays.stream(historyType)
.map(type -> "'" + type + "'")
.collect(Collectors.joining(", "));
return switch (userTypeEnum) {
case PLAYER -> getPlayerNamePunishmentCount(historyTypeSqlList, searchName);
case STAFF -> getStaffNamePunishmentCount(historyTypeSqlList, searchName);
};
}
@Select("""
SELECT COUNT(*) AS punishment_count
FROM all_punishments
WHERE uuid = #{uuid}
AND type IN (${typeList})
""")
Integer getPlayerUuidPunishmentCount(@Param("typeList") String typeList,
@Param("uuid") String uuid);
@Select("""
SELECT COUNT(*) AS punishment_count
FROM all_punishments
WHERE banned_by_uuid = #{uuid}
AND type IN (${typeList})
""")
Integer getStaffUuidPunishmentCount(@Param("typeList") String typeList,
@Param("uuid") String uuid);
default Integer getUuidPunishmentCount(HistoryType historyTypeEnum, UserType userTypeEnum, UUID uuid) {
String[] historyType = getHistoryType(historyTypeEnum);
String historyTypeSqlList = Arrays.stream(historyType)
.map(type -> "'" + type + "'")
.collect(Collectors.joining(", "));
return switch (userTypeEnum) {
case PLAYER -> getPlayerUuidPunishmentCount(historyTypeSqlList, uuid.toString());
case STAFF -> getStaffUuidPunishmentCount(historyTypeSqlList, uuid.toString());
};
}
private String[] getHistoryType(HistoryType historyTypeEnum) {
return switch (historyTypeEnum) {
case ALL -> new String[]{"ban", "mute", "warn"};
case BAN -> new String[]{"ban"};
case MUTE -> new String[]{"mute"};
case KICK -> new String[]{"kick"};
case WARN -> new String[]{"warn"};
};
}
} }

View File

@ -4,6 +4,7 @@ import {HistoryComponent} from './history/history.component';
import {HistoryCount, HistoryService} from '../../api'; import {HistoryCount, HistoryService} from '../../api';
import {NgClass, NgForOf, NgIf} from '@angular/common'; import {NgClass, NgForOf, NgIf} from '@angular/common';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {catchError, map, Observable} from 'rxjs';
@Component({ @Component({
selector: 'app-bans', selector: 'app-bans',
@ -20,18 +21,14 @@ import {FormsModule} from '@angular/forms';
}) })
export class BansComponent implements OnInit { export class BansComponent implements OnInit {
private PAGE_SIZE: number = 10;
public getCurrentButtonId(options: 'all' | 'ban' | 'mute' | 'kick' | 'warn') {
if (options == this.punishmentType) {
return 'currentButton'
}
return null;
}
constructor(public historyApi: HistoryService) { constructor(public historyApi: HistoryService) {
} }
private PAGE_SIZE: number = 10;
private uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
private actualPage: number = 0;
private totalSearchResults: number = -1;
public active: string = ''; public active: string = '';
public userType: 'player' | 'staff' = "player"; public userType: 'player' | 'staff' = "player";
public punishmentType: 'all' | 'ban' | 'mute' | 'kick' | 'warn' = "all"; public punishmentType: 'all' | 'ban' | 'mute' | 'kick' | 'warn' = "all";
@ -47,7 +44,6 @@ export class BansComponent implements OnInit {
kicks: 0, kicks: 0,
warnings: 0 warnings: 0
} }
private actualPage: number = 0;
ngOnInit() { ngOnInit() {
this.historyApi.getUserNames(this.userType, this.punishmentType).subscribe(names => { this.historyApi.getUserNames(this.userType, this.punishmentType).subscribe(names => {
@ -71,6 +67,13 @@ export class BansComponent implements OnInit {
}); });
} }
public getCurrentButtonId(options: 'all' | 'ban' | 'mute' | 'kick' | 'warn') {
if (options == this.punishmentType) {
return 'currentButton'
}
return null;
}
public filterNames() { public filterNames() {
if (!this.searchTerm) { if (!this.searchTerm) {
this.filteredNames = []; this.filteredNames = [];
@ -91,6 +94,26 @@ export class BansComponent implements OnInit {
this.pushState() this.pushState()
this.finalSearchTerm = this.searchTerm; this.finalSearchTerm = this.searchTerm;
this.page = 0; this.page = 0;
if (this.finalSearchTerm.length === 0) {
this.totalSearchResults = -1
return
}
let totalSearchResultsObservable: Observable<number>;
if (this.uuidRegex.test(this.finalSearchTerm)) {
totalSearchResultsObservable = this.historyApi.getTotalResultsForUuidSearch(this.userType, this.punishmentType, this.finalSearchTerm);
} else {
totalSearchResultsObservable = this.historyApi.getTotalResultsForUserSearch(this.userType, this.punishmentType, this.finalSearchTerm);
}
totalSearchResultsObservable.pipe(
map(totalSearchResults => {
this.totalSearchResults = totalSearchResults;
}),
catchError(err => {
console.error(err);
return [];
})
).subscribe();
} }
public changeHistoryType(type: 'player' | 'staff') { public changeHistoryType(type: 'player' | 'staff') {
@ -142,17 +165,20 @@ export class BansComponent implements OnInit {
public getMaxPage() { public getMaxPage() {
const all = this.historyCount.bans + this.historyCount.mutes + this.historyCount.warnings; const all = this.historyCount.bans + this.historyCount.mutes + this.historyCount.warnings;
if (this.finalSearchTerm.length !== 0) {
return Math.floor(this.totalSearchResults / this.PAGE_SIZE);
}
switch (this.punishmentType) { switch (this.punishmentType) {
case 'all': case 'all':
return Math.floor(all / this.PAGE_SIZE) - 1; return Math.floor(all / this.PAGE_SIZE);
case 'ban': case 'ban':
return Math.floor(this.historyCount.bans / this.PAGE_SIZE) - 1; return Math.floor(this.historyCount.bans / this.PAGE_SIZE);
case 'mute': case 'mute':
return Math.floor(this.historyCount.mutes / this.PAGE_SIZE) - 1; return Math.floor(this.historyCount.mutes / this.PAGE_SIZE);
case 'kick': case 'kick':
return Math.floor(this.historyCount.kicks / this.PAGE_SIZE) - 1; return Math.floor(this.historyCount.kicks / this.PAGE_SIZE);
case 'warn': case 'warn':
return Math.floor(this.historyCount.warnings / this.PAGE_SIZE) - 1; return Math.floor(this.historyCount.warnings / this.PAGE_SIZE);
default: default:
return 0; return 0;
} }

View File

@ -53,7 +53,7 @@ export class HistoryComponent implements OnInit, OnChanges {
if (this.uuidRegex.test(this.searchTerm)) { if (this.uuidRegex.test(this.searchTerm)) {
historyObservable = this.historyApi.getHistoryForUuid(this.userType, this.punishmentType, this.searchTerm, this.page); historyObservable = this.historyApi.getHistoryForUuid(this.userType, this.punishmentType, this.searchTerm, this.page);
} else { } else {
historyObservable = this.historyApi.getHistoryForUsers(this.userType, this.punishmentType, this.searchTerm, this.page) historyObservable = this.historyApi.getHistoryForUsers(this.userType, this.punishmentType, this.searchTerm, this.page);
} }
} }
historyObservable.pipe( historyObservable.pipe(

View File

@ -22,6 +22,10 @@ paths:
$ref: './schemas/bans/bans.yml#/getHistoryForAll' $ref: './schemas/bans/bans.yml#/getHistoryForAll'
/history/{userType}/uuid/{type}/{uuid}/{page}: /history/{userType}/uuid/{type}/{uuid}/{page}:
$ref: './schemas/bans/bans.yml#/getHistoryForUuid' $ref: './schemas/bans/bans.yml#/getHistoryForUuid'
/history/{userType}/search-results/uuid/{type}/{uuid}:
$ref: './schemas/bans/bans.yml#/getTotalResultsForUuidSearch'
/history/{userType}/search-results/user/{type}/{user}:
$ref: './schemas/bans/bans.yml#/getTotalResultsForUserSearch'
/history/total: /history/total:
$ref: './schemas/bans/bans.yml#/getTotalPunishments' $ref: './schemas/bans/bans.yml#/getTotalPunishments'
/appeal/update-mail: /appeal/update-mail:

View File

@ -124,6 +124,54 @@ getTotalPunishments:
application/json: application/json:
schema: schema:
$ref: '../generic/errors.yml#/components/schemas/ApiError' $ref: '../generic/errors.yml#/components/schemas/ApiError'
getTotalResultsForUserSearch:
get:
tags:
- history
summary: Gets total results for search query
description: Retrieves the total count of punishments for the search query
operationId: getTotalResultsForUserSearch
parameters:
- $ref: '#/components/parameters/UserType'
- $ref: '#/components/parameters/HistoryType'
- $ref: '#/components/parameters/User'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResults'
default:
description: Unexpected error
content:
application/json:
schema:
$ref: '../generic/errors.yml#/components/schemas/ApiError'
getTotalResultsForUuidSearch:
get:
tags:
- history
summary: Gets total results for search query
description: Retrieves the total count of punishments for the search query
operationId: getTotalResultsForUuidSearch
parameters:
- $ref: '#/components/parameters/UserType'
- $ref: '#/components/parameters/HistoryType'
- $ref: '#/components/parameters/Uuid'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResults'
default:
description: Unexpected error
content:
application/json:
schema:
$ref: '../generic/errors.yml#/components/schemas/ApiError'
components: components:
parameters: parameters:
HistoryType: HistoryType:
@ -164,6 +212,9 @@ components:
type: integer type: integer
description: The page that should be retrieved description: The page that should be retrieved
schemas: schemas:
SearchResults:
type: integer
description: A number representing the total count of results for the search query
PunishmentHistory: PunishmentHistory:
type: array type: array
items: items: