diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 8a10646..d2625f1 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation("org.mybatis:mybatis:3.5.13") testRuntimeOnly("org.junit.platform:junit-platform-launcher") implementation("org.springframework.boot:spring-boot-configuration-processor") + implementation("org.springframework.boot:spring-boot-starter-hateoas") } tasks.withType { diff --git a/backend/src/main/java/com/alttd/altitudeweb/controllers/GlobalExceptionHandler.java b/backend/src/main/java/com/alttd/altitudeweb/controllers/GlobalExceptionHandler.java new file mode 100644 index 0000000..17eb4ef --- /dev/null +++ b/backend/src/main/java/com/alttd/altitudeweb/controllers/GlobalExceptionHandler.java @@ -0,0 +1,19 @@ +package com.alttd.altitudeweb.controllers; + +import com.alttd.altitudeweb.model.ApiErrorDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception ex) { + ApiErrorDto error = new ApiErrorDto(); + error.reason("Error: " + ex.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } +} 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 78a51d2..5c0dfc6 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 @@ -1,21 +1,106 @@ package com.alttd.altitudeweb.controllers.history; import com.alttd.altitudeweb.api.HistoryApi; +import com.alttd.altitudeweb.database.Connection; +import com.alttd.altitudeweb.database.Databases; +import com.alttd.altitudeweb.database.litebans.*; +import com.alttd.altitudeweb.model.PunishmentHistoryDto; +import com.alttd.altitudeweb.model.PunishmentHistoryInnerDto; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@RestController public class HistoryApiController implements HistoryApi { + @Override - public ResponseEntity getHistoryForUsers(String userType, String type, String user) throws Exception { - return null; + public ResponseEntity getHistoryForUsers(String userType, String type, String user) throws Exception { + UserType userTypeEnum = UserType.getUserType(userType); + HistoryType historyTypeEnum = HistoryType.getHistoryType(type); + PunishmentHistoryDto punishmentHistory = new PunishmentHistoryDto(); + CompletableFuture> historyRecords = new CompletableFuture<>(); + Connection.getConnection(Databases.LITE_BANS, configuration -> configuration.addMapper(NameHistoryMapper.class)) + .thenApply(connection -> { + connection.runQuery(sqlSession -> { + log.debug("Loading user names for type {}", type); + List temp = sqlSession.getMapper(NameHistoryMapper.class) + .getRecent(historyTypeEnum, userTypeEnum, user); + historyRecords.complete(temp); + }); + return connection; + }); + historyRecords.join().forEach(historyRecord -> { + PunishmentHistoryInnerDto innerDto = new PunishmentHistoryInnerDto() + .uuid(historyRecord.getUuid()) + .username(historyRecord.getPunishedName()) + .reason(historyRecord.getReason()) + .punishmentUserUuid(historyRecord.getBannedByUuid()) + .punishmentUser(historyRecord.getBannedByName()) + .removedBy(historyRecord.getRemovedByName()) + .punishmentTime(historyRecord.getTime()) + .expiryTime(historyRecord.getUntil()) + .removedReason(historyRecord.getRemovedByReason()) + .type(historyRecord.getType()); + punishmentHistory.add(innerDto); + }); + + return ResponseEntity.ok().body(punishmentHistory); } @Override - public ResponseEntity getHistoryForUuid(String userType, String type, String uuid) throws Exception { - return null; + public ResponseEntity getHistoryForUuid(String userType, String type, String uuid) throws Exception { + UserType userTypeEnum = UserType.getUserType(userType); + HistoryType historyTypeEnum = HistoryType.getHistoryType(type); + PunishmentHistoryDto punishmentHistory = new PunishmentHistoryDto(); + CompletableFuture> historyRecords = new CompletableFuture<>(); + Connection.getConnection(Databases.LITE_BANS, configuration -> configuration.addMapper(UUIDHistoryMapper.class)) + .thenApply(connection -> { + connection.runQuery(sqlSession -> { + log.debug("Loading user names for type {}", type); + List temp = sqlSession.getMapper(UUIDHistoryMapper.class) + .getRecent(historyTypeEnum, userTypeEnum, UUID.fromString(uuid)); + historyRecords.complete(temp); + }); + return connection; + }); + historyRecords.join().forEach(historyRecord -> { + PunishmentHistoryInnerDto innerDto = new PunishmentHistoryInnerDto() + .uuid(historyRecord.getUuid()) + .username(historyRecord.getPunishedName()) + .reason(historyRecord.getReason()) + .punishmentUserUuid(historyRecord.getBannedByUuid()) + .punishmentUser(historyRecord.getBannedByName()) + .removedBy(historyRecord.getRemovedByName()) + .punishmentTime(historyRecord.getTime()) + .expiryTime(historyRecord.getUntil()) + .removedReason(historyRecord.getRemovedByReason()) + .type(historyRecord.getType()); + punishmentHistory.add(innerDto); + }); + + return ResponseEntity.ok().body(punishmentHistory); } @Override - public ResponseEntity getUserNames(String userType, String type) throws Exception { - return null; + public ResponseEntity> getUserNames(String userType, String type) { + UserType userTypeEnum = UserType.getUserType(userType); + HistoryType historyTypeEnum = HistoryType.getHistoryType(type); + CompletableFuture> playerGroupFuture = new CompletableFuture<>(); + Connection.getConnection(Databases.LITE_BANS, configuration -> configuration.addMapper(RecentNamesMapper.class)) + .thenApply(connection -> { + connection.runQuery(sqlSession -> { + log.debug("Loading user names for type {}", type); + List temp = sqlSession.getMapper(RecentNamesMapper.class) + .getRecent(historyTypeEnum, userTypeEnum); + playerGroupFuture.complete(temp); + }); + return connection; + }); + return ResponseEntity.ok().body(playerGroupFuture.join()); } } diff --git a/database/src/main/java/com/alttd/altitudeweb/database/Databases.java b/database/src/main/java/com/alttd/altitudeweb/database/Databases.java index 8656a51..bf76977 100644 --- a/database/src/main/java/com/alttd/altitudeweb/database/Databases.java +++ b/database/src/main/java/com/alttd/altitudeweb/database/Databases.java @@ -5,7 +5,8 @@ import lombok.Getter; @Getter public enum Databases { DEFAULT("web_db"), - LUCK_PERMS("luckperms"); + LUCK_PERMS("luckperms"), + LITE_BANS("litebans"); private final String internalName; diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryRecord.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryRecord.java new file mode 100644 index 0000000..52f5b34 --- /dev/null +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryRecord.java @@ -0,0 +1,17 @@ +package com.alttd.altitudeweb.database.litebans; + +import lombok.Data; + +@Data +public class HistoryRecord { + private String uuid; + private String punishedName; + private String reason; + private String bannedByUuid; + private String bannedByName; + private String removedByName; + private Long time; + private Long until; + private String removedByReason; + private String type; +} diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryType.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryType.java new file mode 100644 index 0000000..fa708f6 --- /dev/null +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/HistoryType.java @@ -0,0 +1,13 @@ +package com.alttd.altitudeweb.database.litebans; + +public enum HistoryType { + ALL, + BANS, + MUTES, + KICKS, + WARNS; + + public static HistoryType getHistoryType(String historyType) { + return HistoryType.valueOf(historyType.toUpperCase()); + } +} diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/NameHistoryMapper.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/NameHistoryMapper.java new file mode 100644 index 0000000..f65826c --- /dev/null +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/NameHistoryMapper.java @@ -0,0 +1,94 @@ +package com.alttd.altitudeweb.database.litebans; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; + +import java.util.ArrayList; +import java.util.List; + +public interface NameHistoryMapper { + @Results({ + @Result(property = "uuid", column = "uuid"), + @Result(property = "punishedName", column = "punished_name"), + @Result(property = "reason", column = "reason"), + @Result(property = "bannedByUuid", column = "banned_by_uuid"), + @Result(property = "bannedByName", column = "banned_by_name"), + @Result(property = "removedByName", column = "removed_by_name"), + @Result(property = "time", column = "time"), + @Result(property = "until", column = "until"), + @Result(property = "removedByReason", column = "removed_by_reason") + }) + @Select(""" + SELECT punishment.uuid, user_table.name AS punished_name, reason, banned_by_uuid, banned_by_name, + removed_by_name, time, until, removed_by_reason + FROM ${tableName} AS punishment + INNER JOIN ( + SELECT history_1.uuid, history_1.name + FROM litebans.litebans_history history_1 + INNER JOIN ( + SELECT uuid, MAX(id) as max_id + FROM litebans.litebans_history + GROUP BY uuid + ) history_2 ON history_1.uuid = history_2.uuid AND history_1.id = history_2.max_id + ) AS user_table ON punishment.uuid = user_table.uuid + WHERE LOWER(punishment.${name_column}) LIKE #{partialName} + """) + List getRecentHistory(@Param("tableName") String tableName, @Param("partialName") String partialName, + @Param("name_column") String nameColumn); + + default List getRecent(HistoryType historyType, UserType userType, String partialName) { + return switch (historyType) { + case ALL -> getRecentAll(userType, partialName); + case BANS -> getRecentBans(userType, partialName); + case MUTES -> getRecentMutes(userType, partialName); + case KICKS -> getRecentKicks(userType, partialName); + case WARNS -> getRecentWarns(userType, partialName); + }; + } + + private List getRecent(String tableName, UserType userType, String partialName) { + switch (userType) { + case PLAYER -> { + return getRecentHistory(tableName, partialName.toLowerCase() + "%", "name"); + } + case STAFF -> { + return getRecentHistory(tableName, partialName.toLowerCase() + "%", "banned_by_name"); + } + default -> throw new IllegalArgumentException("Invalid user type"); + } + } + + private List getRecentBans(UserType userType, String partialName) { + return addType(getRecent("litebans_bans", userType, partialName), "ban"); + } + + private List getRecentKicks(UserType userType, String partialName) { + return addType(getRecent("litebans_kicks", userType, partialName), "kick"); + } + + private List getRecentMutes(UserType userType, String partialName) { + return addType(getRecent("litebans_mutes", userType, partialName), "mute"); + } + + private List getRecentWarns(UserType userType, String partialName) { + return addType(getRecent("litebans_warnings", userType, partialName), "warn"); + } + + private List getRecentAll(UserType userType, String partialName) { + List all = new ArrayList<>(); + all.addAll(getRecentBans(userType, partialName)); + // Removed recent kicks as we do not show those +// all.addAll(getRecentKicks(userType, partialName)); + all.addAll(getRecentMutes(userType, partialName)); + all.addAll(getRecentWarns(userType, partialName)); + return all; + } + + private List addType(List historyRecords, String type) { + historyRecords.forEach(historyRecord -> historyRecord.setType(type)); + return historyRecords; + } + +} diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/RecentNamesMapper.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/RecentNamesMapper.java new file mode 100644 index 0000000..4085c1f --- /dev/null +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/RecentNamesMapper.java @@ -0,0 +1,74 @@ +package com.alttd.altitudeweb.database.litebans; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.ArrayList; +import java.util.List; + +public interface RecentNamesMapper { + @Select(""" + SELECT DISTINCT user_table.name AS punished_name + FROM ${tableName} AS punishment + INNER JOIN ( + SELECT history_1.uuid, history_1.name + FROM litebans.litebans_history history_1 + INNER JOIN ( + SELECT uuid, MAX(id) as max_id + FROM litebans.litebans_history + GROUP BY uuid + ) history_2 ON history_1.uuid = history_2.uuid AND history_1.id = history_2.max_id + ) AS user_table ON punishment.${uuid_column} = user_table.uuid + WHERE punishment.time > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 1 YEAR)) * 1000 + """) + List getRecentHistory(@Param("tableName") String tableName, @Param("uuid_column") String uuidColumn); + + default List getRecent(HistoryType historyType, UserType userType) { + return switch (historyType) { + case ALL -> getRecentAll(userType); + case BANS -> getRecentBans(userType); + case MUTES -> getRecentMutes(userType); + case KICKS -> getRecentKicks(userType); + case WARNS -> getRecentWarns(userType); + }; + } + + private List getRecent(String tableName, UserType userType) { + switch (userType) { + case PLAYER -> { + return getRecentHistory(tableName, "uuid"); + } + case STAFF -> { + return getRecentHistory(tableName, "banned_by_uuid"); + } + default -> throw new IllegalArgumentException("Invalid user type"); + } + } + + private List getRecentBans(UserType userType) { + return getRecent("litebans_bans", userType); + } + + private List getRecentKicks(UserType userType) { + return getRecent("litebans_kicks", userType); + } + + private List getRecentMutes(UserType userType) { + return getRecent("litebans_mutes", userType); + } + + private List getRecentWarns(UserType userType) { + return getRecent("litebans_warnings", userType); + } + + private List getRecentAll(UserType userType) { + List all = new ArrayList<>(); + all.addAll(getRecentBans(userType)); + // Removed recent kicks as we do not show those +// all.addAll(getRecentKicks(userType)); + all.addAll(getRecentMutes(userType)); + all.addAll(getRecentWarns(userType)); + return all; + } + +} diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/UUIDHistoryMapper.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/UUIDHistoryMapper.java new file mode 100644 index 0000000..d48f0d5 --- /dev/null +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/UUIDHistoryMapper.java @@ -0,0 +1,96 @@ +package com.alttd.altitudeweb.database.litebans; + +import com.alttd.altitudeweb.type_handler.UUIDTypeHandler; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public interface UUIDHistoryMapper { + @Results({ + @Result(property = "uuid", column = "uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class), + @Result(property = "punishedName", column = "punished_name"), + @Result(property = "reason", column = "reason"), + @Result(property = "bannedByUuid", column = "banned_by_uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class), + @Result(property = "bannedByName", column = "banned_by_name"), + @Result(property = "removedByName", column = "removed_by_name"), + @Result(property = "time", column = "time"), + @Result(property = "until", column = "until"), + @Result(property = "removedByReason", column = "removed_by_reason") + }) + @Select(""" + SELECT punishment.uuid, user_table.name AS punished_name, reason, banned_by_uuid, banned_by_name, + removed_by_name, time, until, removed_by_reason + FROM ${tableName} AS punishment + INNER JOIN ( + SELECT history_1.uuid, history_1.name + FROM litebans.litebans_history history_1 + INNER JOIN ( + SELECT uuid, MAX(id) as max_id + FROM litebans.litebans_history + GROUP BY uuid + ) history_2 ON history_1.uuid = history_2.uuid AND history_1.id = history_2.max_id + ) AS user_table ON punishment.uuid = user_table.uuid + WHERE punishment.uuid = #{uuid} + """) + List getRecentHistory(@Param("tableName") String tableName, @Param("uuid") String uuid, + @Param("uuid_column") String uuidColumn); + + default List getRecent(HistoryType historyType, UserType userType, UUID uuid) { + return switch (historyType) { + case ALL -> getRecentAll(userType, uuid); + case BANS -> getRecentBans(userType, uuid); + case MUTES -> getRecentMutes(userType, uuid); + case KICKS -> getRecentKicks(userType, uuid); + case WARNS -> getRecentKicks(userType, uuid); + }; + } + + private List getRecent(String tableName, UserType userType, UUID uuid) { + switch (userType) { + case PLAYER -> { + return getRecentHistory(tableName, uuid.toString(), "uuid"); + } + case STAFF -> { + return getRecentHistory(tableName, uuid.toString(), "banned_by_uuid"); + } + default -> throw new IllegalArgumentException("Invalid user type"); + } + } + + private List getRecentBans(UserType userType, UUID uuid) { + return addType(getRecent("litebans_bans", userType, uuid), "ban"); + } + + private List getRecentKicks(UserType userType, UUID uuid) { + return addType(getRecent("litebans_kicks", userType, uuid), "kick"); + } + + private List getRecentMutes(UserType userType, UUID uuid) { + return addType(getRecent("litebans_mutes", userType, uuid), "mute"); + } + + private List getRecentWarns(UserType userType, UUID uuid) { + return addType(getRecent("litebans_warnings", userType, uuid), "warn"); + } + + private List getRecentAll(UserType userType, UUID uuid) { + List all = new ArrayList<>(); + all.addAll(getRecentBans(userType, uuid)); + // Removed recent kicks as we do not show those +// all.addAll(getRecentKicks(userType, uuid)); + all.addAll(getRecentMutes(userType, uuid)); + all.addAll(getRecentWarns(userType, uuid)); + return all; + } + + private List addType(List historyRecords, String type) { + historyRecords.forEach(historyRecord -> historyRecord.setType(type)); + return historyRecords; + } + +} diff --git a/database/src/main/java/com/alttd/altitudeweb/database/litebans/UserType.java b/database/src/main/java/com/alttd/altitudeweb/database/litebans/UserType.java new file mode 100644 index 0000000..326a2cb --- /dev/null +++ b/database/src/main/java/com/alttd/altitudeweb/database/litebans/UserType.java @@ -0,0 +1,10 @@ +package com.alttd.altitudeweb.database.litebans; + +public enum UserType { + PLAYER, + STAFF; + + public static UserType getUserType(String userType) { + return UserType.valueOf(userType.toUpperCase()); + } +} diff --git a/open_api/src/main/resources/api.yml b/open_api/src/main/resources/api.yml index cfbb434..a6726e1 100644 --- a/open_api/src/main/resources/api.yml +++ b/open_api/src/main/resources/api.yml @@ -16,7 +16,7 @@ paths: $ref: './schemas/team/team.yml#/getTeam' /history/{userType}/search/{type}: $ref: './schemas/bans/bans.yml#/getUserNames' - /history/{userType}/{type}/{user}: + /history/{userType}/name/{type}/{user}: $ref: './schemas/bans/bans.yml#/getHistoryForUsers' - /history/{userType}/{type}/{uuid}: + /history/{userType}/uuid/{type}/{uuid}: $ref: './schemas/bans/bans.yml#/getHistoryForUuid' diff --git a/open_api/src/main/resources/schemas/bans/bans.yml b/open_api/src/main/resources/schemas/bans/bans.yml index 888093b..5bbda03 100644 --- a/open_api/src/main/resources/schemas/bans/bans.yml +++ b/open_api/src/main/resources/schemas/bans/bans.yml @@ -1,31 +1,31 @@ getUserNames: - post: + get: tags: - history summary: Get user names description: Get all user names with punishment history of the specified type operationId: getUserNames - parameters: - - $ref: '#/components/parameters/UserType' - - $ref: '#/components/parameters/HistoryType' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - type: array - items: - type: string - description: A list of users - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "../generic/errors.yml#/components/schemas/ApiError" + parameters: + - $ref: '#/components/parameters/UserType' + - $ref: '#/components/parameters/HistoryType' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: array + items: + type: string + description: A list of users + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "../generic/errors.yml#/components/schemas/ApiError" getHistoryForUsers: - post: + get: tags: - history summary: Get history for users @@ -33,25 +33,25 @@ getHistoryForUsers: Get all users with punishment history of the specified type, where the user name starts with the search query operationId: getHistoryForUsers - 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/PunishmentHistory' - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "../generic/errors.yml#/components/schemas/ApiError" + 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/PunishmentHistory' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "../generic/errors.yml#/components/schemas/ApiError" getHistoryForUuid: - post: + get: tags: - history summary: Get user @@ -59,23 +59,23 @@ getHistoryForUuid: Get all users with punishment history of the specified type, where the user uuid matches operationId: getHistoryForUuid - 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/PunishmentHistory' - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "../generic/errors.yml#/components/schemas/ApiError" + 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/PunishmentHistory' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: "../generic/errors.yml#/components/schemas/ApiError" components: parameters: HistoryType: