From ecee377f0141491097616d68a6c0b1e9a6c38403 Mon Sep 17 00:00:00 2001 From: Teriuihi Date: Fri, 18 Apr 2025 20:43:17 +0200 Subject: [PATCH] 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. --- .../history/HistoryApiController.java | 42 ++++++++++ .../database/litebans/HistoryCountMapper.java | 77 +++++++++++++++++++ frontend/src/app/bans/bans.component.ts | 56 ++++++++++---- .../src/app/bans/history/history.component.ts | 2 +- open_api/src/main/resources/api.yml | 4 + .../src/main/resources/schemas/bans/bans.yml | 51 ++++++++++++ 6 files changed, 216 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/com/alttd/altitudeweb/controllers/history/HistoryApiController.java b/backend/src/main/java/com/alttd/altitudeweb/controllers/history/HistoryApiController.java index 22b6362..6e35c60 100644 --- a/backend/src/main/java/com/alttd/altitudeweb/controllers/history/HistoryApiController.java +++ b/backend/src/main/java/com/alttd/altitudeweb/controllers/history/HistoryApiController.java @@ -111,6 +111,48 @@ public class HistoryApiController implements HistoryApi { return mapHistoryCount(historyCountCompletableFuture.join()); } + @Override + public ResponseEntity getTotalResultsForUserSearch(String userType, String type, String user) { + UserType userTypeEnum = UserType.getUserType(userType); + HistoryType historyTypeEnum = HistoryType.getHistoryType(type); + CompletableFuture 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 getTotalResultsForUuidSearch(String userType, String type, String uuid) { + UserType userTypeEnum = UserType.getUserType(userType); + HistoryType historyTypeEnum = HistoryType.getHistoryType(type); + CompletableFuture 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 mapHistoryCount(HistoryCount historyCount) { HistoryCountDto historyCountDto = new HistoryCountDto(); historyCountDto.setBans(historyCount.getBans()); diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryCountMapper.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryCountMapper.java index d237a0f..a63ca88 100644 --- a/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryCountMapper.java +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryCountMapper.java @@ -1,7 +1,12 @@ package com.alttd.altitudeweb.database.litebans; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import java.util.Arrays; +import java.util.UUID; +import java.util.stream.Collectors; + public interface HistoryCountMapper { /** * Gets the total count of punishments from all LiteBans tables. @@ -17,4 +22,76 @@ public interface HistoryCountMapper { """ }) 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"}; + }; + } } diff --git a/frontend/src/app/bans/bans.component.ts b/frontend/src/app/bans/bans.component.ts index a92a9fb..09d5abb 100644 --- a/frontend/src/app/bans/bans.component.ts +++ b/frontend/src/app/bans/bans.component.ts @@ -4,6 +4,7 @@ import {HistoryComponent} from './history/history.component'; import {HistoryCount, HistoryService} from '../../api'; import {NgClass, NgForOf, NgIf} from '@angular/common'; import {FormsModule} from '@angular/forms'; +import {catchError, map, Observable} from 'rxjs'; @Component({ selector: 'app-bans', @@ -20,18 +21,14 @@ import {FormsModule} from '@angular/forms'; }) 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) { } + 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 userType: 'player' | 'staff' = "player"; public punishmentType: 'all' | 'ban' | 'mute' | 'kick' | 'warn' = "all"; @@ -47,7 +44,6 @@ export class BansComponent implements OnInit { kicks: 0, warnings: 0 } - private actualPage: number = 0; ngOnInit() { 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() { if (!this.searchTerm) { this.filteredNames = []; @@ -91,6 +94,26 @@ export class BansComponent implements OnInit { this.pushState() this.finalSearchTerm = this.searchTerm; this.page = 0; + if (this.finalSearchTerm.length === 0) { + this.totalSearchResults = -1 + return + } + + let totalSearchResultsObservable: Observable; + 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') { @@ -142,17 +165,20 @@ export class BansComponent implements OnInit { public getMaxPage() { 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) { case 'all': - return Math.floor(all / this.PAGE_SIZE) - 1; + return Math.floor(all / this.PAGE_SIZE); case 'ban': - return Math.floor(this.historyCount.bans / this.PAGE_SIZE) - 1; + return Math.floor(this.historyCount.bans / this.PAGE_SIZE); case 'mute': - return Math.floor(this.historyCount.mutes / this.PAGE_SIZE) - 1; + return Math.floor(this.historyCount.mutes / this.PAGE_SIZE); case 'kick': - return Math.floor(this.historyCount.kicks / this.PAGE_SIZE) - 1; + return Math.floor(this.historyCount.kicks / this.PAGE_SIZE); case 'warn': - return Math.floor(this.historyCount.warnings / this.PAGE_SIZE) - 1; + return Math.floor(this.historyCount.warnings / this.PAGE_SIZE); default: return 0; } diff --git a/frontend/src/app/bans/history/history.component.ts b/frontend/src/app/bans/history/history.component.ts index a44039c..184246a 100644 --- a/frontend/src/app/bans/history/history.component.ts +++ b/frontend/src/app/bans/history/history.component.ts @@ -53,7 +53,7 @@ export class HistoryComponent implements OnInit, OnChanges { if (this.uuidRegex.test(this.searchTerm)) { historyObservable = this.historyApi.getHistoryForUuid(this.userType, this.punishmentType, this.searchTerm, this.page); } 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( diff --git a/open_api/src/main/resources/api.yml b/open_api/src/main/resources/api.yml index c4a5bd1..b73aad3 100644 --- a/open_api/src/main/resources/api.yml +++ b/open_api/src/main/resources/api.yml @@ -22,6 +22,10 @@ paths: $ref: './schemas/bans/bans.yml#/getHistoryForAll' /history/{userType}/uuid/{type}/{uuid}/{page}: $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: $ref: './schemas/bans/bans.yml#/getTotalPunishments' /appeal/update-mail: diff --git a/open_api/src/main/resources/schemas/bans/bans.yml b/open_api/src/main/resources/schemas/bans/bans.yml index dba4761..df67f19 100644 --- a/open_api/src/main/resources/schemas/bans/bans.yml +++ b/open_api/src/main/resources/schemas/bans/bans.yml @@ -124,6 +124,54 @@ getTotalPunishments: application/json: schema: $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: parameters: HistoryType: @@ -164,6 +212,9 @@ components: type: integer description: The page that should be retrieved schemas: + SearchResults: + type: integer + description: A number representing the total count of results for the search query PunishmentHistory: type: array items: