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: