Compare commits
3 Commits
a1912b96d8
...
d54a7e51ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d54a7e51ee | ||
|
|
6be6944dea | ||
|
|
3babde5513 |
|
|
@ -3,11 +3,11 @@ package com.alttd.altitudeweb.controllers.history;
|
||||||
import com.alttd.altitudeweb.api.HistoryApi;
|
import com.alttd.altitudeweb.api.HistoryApi;
|
||||||
import com.alttd.altitudeweb.controllers.limits.RateLimit;
|
import com.alttd.altitudeweb.controllers.limits.RateLimit;
|
||||||
import com.alttd.altitudeweb.model.HistoryCountDto;
|
import com.alttd.altitudeweb.model.HistoryCountDto;
|
||||||
|
import com.alttd.altitudeweb.model.PunishmentHistoryListDto;
|
||||||
import com.alttd.altitudeweb.setup.Connection;
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
import com.alttd.altitudeweb.database.Databases;
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
import com.alttd.altitudeweb.database.litebans.*;
|
import com.alttd.altitudeweb.database.litebans.*;
|
||||||
import com.alttd.altitudeweb.model.PunishmentHistoryDto;
|
import com.alttd.altitudeweb.model.PunishmentHistoryDto;
|
||||||
import com.alttd.altitudeweb.model.PunishmentHistoryInnerDto;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
@ -19,19 +19,19 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RateLimit(limit = 30, timeValue = 1, timeUnit = TimeUnit.MINUTES)
|
@RateLimit(limit = 30, timeValue = 10, timeUnit = TimeUnit.SECONDS)
|
||||||
public class HistoryApiController implements HistoryApi {
|
public class HistoryApiController implements HistoryApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<PunishmentHistoryDto> getHistoryForAll(String userType, String type, Integer page) {
|
public ResponseEntity<PunishmentHistoryListDto> getHistoryForAll(String userType, String type, Integer page) {
|
||||||
return getHistoryForUsers(userType, type, "", page);
|
return getHistoryForUsers(userType, type, "", page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<PunishmentHistoryDto> getHistoryForUsers(String userType, String type, String user, Integer page) {
|
public ResponseEntity<PunishmentHistoryListDto> getHistoryForUsers(String userType, String type, String user, Integer page) {
|
||||||
UserType userTypeEnum = UserType.getUserType(userType);
|
UserType userTypeEnum = UserType.getUserType(userType);
|
||||||
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
||||||
PunishmentHistoryDto punishmentHistory = new PunishmentHistoryDto();
|
PunishmentHistoryListDto punishmentHistoryList = new PunishmentHistoryListDto();
|
||||||
CompletableFuture<List<HistoryRecord>> historyRecords = new CompletableFuture<>();
|
CompletableFuture<List<HistoryRecord>> historyRecords = new CompletableFuture<>();
|
||||||
|
|
||||||
Connection.getConnection(Databases.LITE_BANS)
|
Connection.getConnection(Databases.LITE_BANS)
|
||||||
|
|
@ -46,14 +46,14 @@ public class HistoryApiController implements HistoryApi {
|
||||||
historyRecords.completeExceptionally(e);
|
historyRecords.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return mapPunishmentHistory(punishmentHistory, historyRecords);
|
return mapPunishmentHistory(punishmentHistoryList, historyRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<PunishmentHistoryDto> getHistoryForUuid(String userType, String type, String uuid, Integer page) {
|
public ResponseEntity<PunishmentHistoryListDto> getHistoryForUuid(String userType, String type, String uuid, Integer page) {
|
||||||
UserType userTypeEnum = UserType.getUserType(userType);
|
UserType userTypeEnum = UserType.getUserType(userType);
|
||||||
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
||||||
PunishmentHistoryDto punishmentHistory = new PunishmentHistoryDto();
|
PunishmentHistoryListDto punishmentHistoryList = new PunishmentHistoryListDto();
|
||||||
CompletableFuture<List<HistoryRecord>> historyRecords = new CompletableFuture<>();
|
CompletableFuture<List<HistoryRecord>> historyRecords = new CompletableFuture<>();
|
||||||
|
|
||||||
Connection.getConnection(Databases.LITE_BANS)
|
Connection.getConnection(Databases.LITE_BANS)
|
||||||
|
|
@ -68,7 +68,7 @@ public class HistoryApiController implements HistoryApi {
|
||||||
historyRecords.completeExceptionally(e);
|
historyRecords.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return mapPunishmentHistory(punishmentHistory, historyRecords);
|
return mapPunishmentHistory(punishmentHistoryList, historyRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -153,6 +153,26 @@ public class HistoryApiController implements HistoryApi {
|
||||||
return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
|
return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<PunishmentHistoryDto> getHistoryById(String type, Integer id) {
|
||||||
|
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
||||||
|
CompletableFuture<HistoryRecord> historyRecordCompletableFuture = new CompletableFuture<>();
|
||||||
|
|
||||||
|
Connection.getConnection(Databases.LITE_BANS)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
log.debug("Loading history by id");
|
||||||
|
try {
|
||||||
|
HistoryRecord punishment = sqlSession.getMapper(IdHistoryMapper.class)
|
||||||
|
.getRecentHistory(historyTypeEnum, id);
|
||||||
|
historyRecordCompletableFuture.complete(punishment);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load history count", e);
|
||||||
|
historyRecordCompletableFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ResponseEntity.ok().body(mapPunishmentHistory(historyRecordCompletableFuture.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());
|
||||||
|
|
@ -162,28 +182,33 @@ public class HistoryApiController implements HistoryApi {
|
||||||
return ResponseEntity.ok().body(historyCountDto);
|
return ResponseEntity.ok().body(historyCountDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<PunishmentHistoryDto> mapPunishmentHistory(PunishmentHistoryDto punishmentHistory, CompletableFuture<List<HistoryRecord>> historyRecords) {
|
private ResponseEntity<PunishmentHistoryListDto> mapPunishmentHistory(PunishmentHistoryListDto punishmentHistoryList, CompletableFuture<List<HistoryRecord>> historyRecords) {
|
||||||
historyRecords.join().forEach(historyRecord -> {
|
historyRecords.join().forEach(historyRecord -> {
|
||||||
PunishmentHistoryInnerDto.TypeEnum type = switch (historyRecord.getType().toLowerCase()) {
|
PunishmentHistoryDto innerDto = mapPunishmentHistory(historyRecord);
|
||||||
case "ban" -> PunishmentHistoryInnerDto.TypeEnum.BAN;
|
punishmentHistoryList.add(innerDto);
|
||||||
case "mute" -> PunishmentHistoryInnerDto.TypeEnum.MUTE;
|
|
||||||
case "warn" -> PunishmentHistoryInnerDto.TypeEnum.WARN;
|
|
||||||
case "kick" -> PunishmentHistoryInnerDto.TypeEnum.KICK;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: " + historyRecord.getType());
|
|
||||||
};
|
|
||||||
PunishmentHistoryInnerDto innerDto = new PunishmentHistoryInnerDto()
|
|
||||||
.uuid(historyRecord.getUuid())
|
|
||||||
.username(historyRecord.getPunishedName())
|
|
||||||
.reason(historyRecord.getReason())
|
|
||||||
.punishedByUuid(historyRecord.getBannedByUuid())
|
|
||||||
.punishedBy(historyRecord.getBannedByName())
|
|
||||||
.removedBy(historyRecord.getRemovedByName())
|
|
||||||
.punishmentTime(historyRecord.getTime())
|
|
||||||
.expiryTime(historyRecord.getUntil())
|
|
||||||
.removedReason(historyRecord.getRemovedByReason())
|
|
||||||
.type(type);
|
|
||||||
punishmentHistory.add(innerDto);
|
|
||||||
});
|
});
|
||||||
return ResponseEntity.ok().body(punishmentHistory);
|
return ResponseEntity.ok().body(punishmentHistoryList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PunishmentHistoryDto mapPunishmentHistory(HistoryRecord historyRecord) {
|
||||||
|
PunishmentHistoryDto.TypeEnum type = switch (historyRecord.getType().toLowerCase()) {
|
||||||
|
case "ban" -> PunishmentHistoryDto.TypeEnum.BAN;
|
||||||
|
case "mute" -> PunishmentHistoryDto.TypeEnum.MUTE;
|
||||||
|
case "warn" -> PunishmentHistoryDto.TypeEnum.WARN;
|
||||||
|
case "kick" -> PunishmentHistoryDto.TypeEnum.KICK;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + historyRecord.getType());
|
||||||
|
};
|
||||||
|
return new PunishmentHistoryDto()
|
||||||
|
.uuid(historyRecord.getUuid())
|
||||||
|
.username(historyRecord.getPunishedName())
|
||||||
|
.reason(historyRecord.getReason())
|
||||||
|
.punishedByUuid(historyRecord.getBannedByUuid())
|
||||||
|
.punishedBy(historyRecord.getBannedByName())
|
||||||
|
.removedBy(historyRecord.getRemovedByName())
|
||||||
|
.punishmentTime(historyRecord.getTime())
|
||||||
|
.expiryTime(historyRecord.getUntil())
|
||||||
|
.removedReason(historyRecord.getRemovedByReason())
|
||||||
|
.type(type)
|
||||||
|
.id(historyRecord.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ dependencies {
|
||||||
implementation("org.mybatis:mybatis:3.5.13")
|
implementation("org.mybatis:mybatis:3.5.13")
|
||||||
compileOnly("org.slf4j:slf4j-api:2.0.17")
|
compileOnly("org.slf4j:slf4j-api:2.0.17")
|
||||||
compileOnly("org.slf4j:slf4j-simple:2.0.17")
|
compileOnly("org.slf4j:slf4j-simple:2.0.17")
|
||||||
|
compileOnly("org.jetbrains:annotations:26.0.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.test {
|
tasks.test {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import lombok.Data;
|
||||||
@Data
|
@Data
|
||||||
public class HistoryRecord {
|
public class HistoryRecord {
|
||||||
private String uuid;
|
private String uuid;
|
||||||
|
private int id;
|
||||||
private String punishedName;
|
private String punishedName;
|
||||||
private String reason;
|
private String reason;
|
||||||
private String bannedByUuid;
|
private String bannedByUuid;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
public interface IdHistoryMapper {
|
||||||
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
|
@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.id,
|
||||||
|
punishment.uuid,
|
||||||
|
user_lookup.name AS punished_name,
|
||||||
|
punishment.reason,
|
||||||
|
punishment.banned_by_uuid,
|
||||||
|
punishment.banned_by_name,
|
||||||
|
punishment.removed_by_name,
|
||||||
|
punishment.time,
|
||||||
|
punishment.until,
|
||||||
|
punishment.removed_by_reason
|
||||||
|
FROM (SELECT *
|
||||||
|
FROM ${table_name} AS punishment
|
||||||
|
WHERE punishment.id = #{id})
|
||||||
|
AS punishment
|
||||||
|
INNER JOIN user_lookup
|
||||||
|
ON user_lookup.uuid = punishment.uuid
|
||||||
|
""")
|
||||||
|
HistoryRecord getHistoryForId(@Param("table_name") String tableName,
|
||||||
|
@Param("type") String type,
|
||||||
|
@Param("id") int id);
|
||||||
|
|
||||||
|
default HistoryRecord getRecentHistory(HistoryType historyType, int id) {
|
||||||
|
HistoryRecord historyRecord = switch (historyType) {
|
||||||
|
case ALL -> throw new IllegalArgumentException("HistoryType.ALL is not supported");
|
||||||
|
case BAN -> getHistoryForId("litebans_bans", "ban", id);
|
||||||
|
case MUTE -> getHistoryForId("litebans_mutes", "mute", id);
|
||||||
|
case KICK -> getHistoryForId("litebans_kicks", "kick", id);
|
||||||
|
case WARN -> getHistoryForId("litebans_warnings", "warn", id);
|
||||||
|
};
|
||||||
|
historyRecord.setType(historyType.name().toLowerCase());
|
||||||
|
return historyRecord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Result;
|
import org.apache.ibatis.annotations.Result;
|
||||||
import org.apache.ibatis.annotations.Results;
|
import org.apache.ibatis.annotations.Results;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ public interface NameHistoryMapper {
|
||||||
* order
|
* order
|
||||||
*/
|
*/
|
||||||
@Results({
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
@Result(property = "uuid", column = "uuid"),
|
@Result(property = "uuid", column = "uuid"),
|
||||||
@Result(property = "punishedName", column = "punished_name"),
|
@Result(property = "punishedName", column = "punished_name"),
|
||||||
@Result(property = "reason", column = "reason"),
|
@Result(property = "reason", column = "reason"),
|
||||||
|
|
@ -36,7 +38,8 @@ public interface NameHistoryMapper {
|
||||||
@Result(property = "type", column = "type")
|
@Result(property = "type", column = "type")
|
||||||
})
|
})
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT all_punishments.uuid,
|
SELECT id,
|
||||||
|
all_punishments.uuid,
|
||||||
user_lookup.name AS punished_name,
|
user_lookup.name AS punished_name,
|
||||||
reason,
|
reason,
|
||||||
banned_by_uuid,
|
banned_by_uuid,
|
||||||
|
|
@ -62,7 +65,6 @@ public interface NameHistoryMapper {
|
||||||
* Retrieves a list of all types of recent punishment history records sorted
|
* Retrieves a list of all types of recent punishment history records sorted
|
||||||
* in descending time order. This result does NOT contain kicks history
|
* in descending time order. This result does NOT contain kicks history
|
||||||
*
|
*
|
||||||
* @param nameColumn the column name in the database indicating the name to use for filtering
|
|
||||||
* @param limit the maximum number of records to fetch
|
* @param limit the maximum number of records to fetch
|
||||||
* @param offset the starting offset position of the result set
|
* @param offset the starting offset position of the result set
|
||||||
*
|
*
|
||||||
|
|
@ -70,6 +72,7 @@ public interface NameHistoryMapper {
|
||||||
* order
|
* order
|
||||||
*/
|
*/
|
||||||
@Results({
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
@Result(property = "uuid", column = "uuid"),
|
@Result(property = "uuid", column = "uuid"),
|
||||||
@Result(property = "punishedName", column = "punished_name"),
|
@Result(property = "punishedName", column = "punished_name"),
|
||||||
@Result(property = "reason", column = "reason"),
|
@Result(property = "reason", column = "reason"),
|
||||||
|
|
@ -82,17 +85,19 @@ public interface NameHistoryMapper {
|
||||||
@Result(property = "type", column = "type")
|
@Result(property = "type", column = "type")
|
||||||
})
|
})
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT punishments.uuid,
|
SELECT punishment.id,
|
||||||
|
punishment.uuid,
|
||||||
user_lookup.name AS punished_name,
|
user_lookup.name AS punished_name,
|
||||||
punishments.reason,
|
punishment.reason,
|
||||||
punishments.banned_by_uuid,
|
punishment.banned_by_uuid,
|
||||||
punishments.banned_by_name,
|
punishment.banned_by_name,
|
||||||
punishments.removed_by_name,
|
punishment.removed_by_name,
|
||||||
punishments.time,
|
punishment.time,
|
||||||
punishments.until,
|
punishment.until,
|
||||||
punishments.removed_by_reason,
|
punishment.removed_by_reason,
|
||||||
punishments.type
|
punishment.type
|
||||||
FROM (SELECT uuid,
|
FROM (SELECT uuid,
|
||||||
|
id,
|
||||||
reason,
|
reason,
|
||||||
banned_by_uuid,
|
banned_by_uuid,
|
||||||
banned_by_name,
|
banned_by_name,
|
||||||
|
|
@ -103,9 +108,9 @@ public interface NameHistoryMapper {
|
||||||
type
|
type
|
||||||
FROM all_punishments
|
FROM all_punishments
|
||||||
ORDER BY time DESC
|
ORDER BY time DESC
|
||||||
LIMIT #{limit} OFFSET #{offset}) AS punishments
|
LIMIT #{limit} OFFSET #{offset}) AS punishment
|
||||||
INNER JOIN user_lookup
|
INNER JOIN user_lookup
|
||||||
ON user_lookup.uuid = punishments.uuid;
|
ON user_lookup.uuid = punishment.uuid;
|
||||||
""")
|
""")
|
||||||
List<HistoryRecord> getRecentAllHistory(@Param("limit") int limit,
|
List<HistoryRecord> getRecentAllHistory(@Param("limit") int limit,
|
||||||
@Param("offset") int offset);
|
@Param("offset") int offset);
|
||||||
|
|
@ -124,6 +129,7 @@ public interface NameHistoryMapper {
|
||||||
* order
|
* order
|
||||||
*/
|
*/
|
||||||
@Results({
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
@Result(property = "uuid", column = "uuid"),
|
@Result(property = "uuid", column = "uuid"),
|
||||||
@Result(property = "punishedName", column = "punished_name"),
|
@Result(property = "punishedName", column = "punished_name"),
|
||||||
@Result(property = "reason", column = "reason"),
|
@Result(property = "reason", column = "reason"),
|
||||||
|
|
@ -135,7 +141,8 @@ public interface NameHistoryMapper {
|
||||||
@Result(property = "removedByReason", column = "removed_by_reason")
|
@Result(property = "removedByReason", column = "removed_by_reason")
|
||||||
})
|
})
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT punishment.uuid,
|
SELECT punishment.id,
|
||||||
|
punishment.uuid,
|
||||||
user_lookup.name AS punished_name,
|
user_lookup.name AS punished_name,
|
||||||
punishment.reason,
|
punishment.reason,
|
||||||
punishment.banned_by_uuid,
|
punishment.banned_by_uuid,
|
||||||
|
|
@ -162,8 +169,6 @@ public interface NameHistoryMapper {
|
||||||
* parameters. The records are sorted in descending order by time and limited to a specified range.
|
* parameters. The records are sorted in descending order by time and limited to a specified range.
|
||||||
*
|
*
|
||||||
* @param tableName the name of the database table to query the history records from
|
* @param tableName the name of the database table to query the history records from
|
||||||
* @param partialName a partial or complete name of the user to filter the records, case-insensitive
|
|
||||||
* @param nameColumn the column name in the database indicating the name to use for filtering
|
|
||||||
* @param limit the maximum number of records to fetch
|
* @param limit the maximum number of records to fetch
|
||||||
* @param offset the starting offset position of the result set
|
* @param offset the starting offset position of the result set
|
||||||
*
|
*
|
||||||
|
|
@ -171,6 +176,7 @@ public interface NameHistoryMapper {
|
||||||
* order
|
* order
|
||||||
*/
|
*/
|
||||||
@Results({
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
@Result(property = "uuid", column = "uuid"),
|
@Result(property = "uuid", column = "uuid"),
|
||||||
@Result(property = "punishedName", column = "punished_name"),
|
@Result(property = "punishedName", column = "punished_name"),
|
||||||
@Result(property = "reason", column = "reason"),
|
@Result(property = "reason", column = "reason"),
|
||||||
|
|
@ -182,7 +188,8 @@ public interface NameHistoryMapper {
|
||||||
@Result(property = "removedByReason", column = "removed_by_reason")
|
@Result(property = "removedByReason", column = "removed_by_reason")
|
||||||
})
|
})
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT punishment.uuid,
|
SELECT punishment.id,
|
||||||
|
punishment.uuid,
|
||||||
user_lookup.name AS punished_name,
|
user_lookup.name AS punished_name,
|
||||||
punishment.reason,
|
punishment.reason,
|
||||||
punishment.banned_by_uuid,
|
punishment.banned_by_uuid,
|
||||||
|
|
@ -192,7 +199,8 @@ public interface NameHistoryMapper {
|
||||||
punishment.until,
|
punishment.until,
|
||||||
punishment.removed_by_reason
|
punishment.removed_by_reason
|
||||||
FROM (
|
FROM (
|
||||||
SELECT uuid,
|
SELECT id,
|
||||||
|
uuid,
|
||||||
reason,
|
reason,
|
||||||
banned_by_uuid,
|
banned_by_uuid,
|
||||||
banned_by_name,
|
banned_by_name,
|
||||||
|
|
@ -254,23 +262,23 @@ public interface NameHistoryMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentBans(UserType userType, String partialName, int page) {
|
private @NotNull List<HistoryRecord> getRecentBans(UserType userType, String partialName, int page) {
|
||||||
return addType(getRecent("litebans_bans", userType, partialName, page), "ban");
|
return addType(getRecent("litebans_bans", userType, partialName, page), "ban");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentKicks(UserType userType, String partialName, int page) {
|
private @NotNull List<HistoryRecord> getRecentKicks(UserType userType, String partialName, int page) {
|
||||||
return addType(getRecent("litebans_kicks", userType, partialName, page), "kick");
|
return addType(getRecent("litebans_kicks", userType, partialName, page), "kick");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentMutes(UserType userType, String partialName, int page) {
|
private @NotNull List<HistoryRecord> getRecentMutes(UserType userType, String partialName, int page) {
|
||||||
return addType(getRecent("litebans_mutes", userType, partialName, page), "mute");
|
return addType(getRecent("litebans_mutes", userType, partialName, page), "mute");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentWarns(UserType userType, String partialName, int page) {
|
private @NotNull List<HistoryRecord> getRecentWarns(UserType userType, String partialName, int page) {
|
||||||
return addType(getRecent("litebans_warnings", userType, partialName, page), "warn");
|
return addType(getRecent("litebans_warnings", userType, partialName, page), "warn");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> addType(List<HistoryRecord> historyRecords, String type) {
|
private @NotNull List<HistoryRecord> addType(@NotNull List<HistoryRecord> historyRecords, String type) {
|
||||||
historyRecords.forEach(historyRecord -> historyRecord.setType(type));
|
historyRecords.forEach(historyRecord -> historyRecord.setType(type));
|
||||||
return historyRecords;
|
return historyRecords;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package com.alttd.altitudeweb.database.litebans;
|
package com.alttd.altitudeweb.database.litebans;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.type_handler.UUIDTypeHandler;
|
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Result;
|
import org.apache.ibatis.annotations.Result;
|
||||||
import org.apache.ibatis.annotations.Results;
|
import org.apache.ibatis.annotations.Results;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -14,10 +15,11 @@ public interface UUIDHistoryMapper {
|
||||||
int PAGE_SIZE = 10;
|
int PAGE_SIZE = 10;
|
||||||
|
|
||||||
@Results({
|
@Results({
|
||||||
@Result(property = "uuid", column = "uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class),
|
@Result(property = "id", column = "id"),
|
||||||
|
@Result(property = "uuid", column = "uuid"),
|
||||||
@Result(property = "punishedName", column = "punished_name"),
|
@Result(property = "punishedName", column = "punished_name"),
|
||||||
@Result(property = "reason", column = "reason"),
|
@Result(property = "reason", column = "reason"),
|
||||||
@Result(property = "bannedByUuid", column = "banned_by_uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class),
|
@Result(property = "bannedByUuid", column = "banned_by_uuid"),
|
||||||
@Result(property = "bannedByName", column = "banned_by_name"),
|
@Result(property = "bannedByName", column = "banned_by_name"),
|
||||||
@Result(property = "removedByName", column = "removed_by_name"),
|
@Result(property = "removedByName", column = "removed_by_name"),
|
||||||
@Result(property = "time", column = "time"),
|
@Result(property = "time", column = "time"),
|
||||||
|
|
@ -26,7 +28,7 @@ public interface UUIDHistoryMapper {
|
||||||
@Result(property = "type", column = "type")
|
@Result(property = "type", column = "type")
|
||||||
})
|
})
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT all_punishments.uuid, user_lookup.name AS punished_name, reason, banned_by_uuid, banned_by_name,
|
SELECT id, all_punishments.uuid, user_lookup.name AS punished_name, reason, banned_by_uuid, banned_by_name,
|
||||||
removed_by_name, time, until, removed_by_reason, type
|
removed_by_name, time, until, removed_by_reason, type
|
||||||
FROM all_punishments
|
FROM all_punishments
|
||||||
INNER JOIN user_lookup
|
INNER JOIN user_lookup
|
||||||
|
|
@ -41,10 +43,11 @@ public interface UUIDHistoryMapper {
|
||||||
@Param("offset") int offset);
|
@Param("offset") int offset);
|
||||||
|
|
||||||
@Results({
|
@Results({
|
||||||
@Result(property = "uuid", column = "uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class),
|
@Result(property = "id", column = "id"),
|
||||||
|
@Result(property = "uuid", column = "uuid"),
|
||||||
@Result(property = "punishedName", column = "punished_name"),
|
@Result(property = "punishedName", column = "punished_name"),
|
||||||
@Result(property = "reason", column = "reason"),
|
@Result(property = "reason", column = "reason"),
|
||||||
@Result(property = "bannedByUuid", column = "banned_by_uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class),
|
@Result(property = "bannedByUuid", column = "banned_by_uuid"),
|
||||||
@Result(property = "bannedByName", column = "banned_by_name"),
|
@Result(property = "bannedByName", column = "banned_by_name"),
|
||||||
@Result(property = "removedByName", column = "removed_by_name"),
|
@Result(property = "removedByName", column = "removed_by_name"),
|
||||||
@Result(property = "time", column = "time"),
|
@Result(property = "time", column = "time"),
|
||||||
|
|
@ -52,7 +55,7 @@ public interface UUIDHistoryMapper {
|
||||||
@Result(property = "removedByReason", column = "removed_by_reason")
|
@Result(property = "removedByReason", column = "removed_by_reason")
|
||||||
})
|
})
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT punishment.uuid, user_lookup.name AS punished_name, reason, banned_by_uuid, banned_by_name,
|
SELECT punishment.id, punishment.uuid, user_lookup.name AS punished_name, reason, banned_by_uuid, banned_by_name,
|
||||||
removed_by_name, time, until, removed_by_reason
|
removed_by_name, time, until, removed_by_reason
|
||||||
FROM ${tableName} AS punishment
|
FROM ${tableName} AS punishment
|
||||||
INNER JOIN user_lookup ON user_lookup.uuid = punishment.uuid
|
INNER JOIN user_lookup ON user_lookup.uuid = punishment.uuid
|
||||||
|
|
@ -66,7 +69,8 @@ public interface UUIDHistoryMapper {
|
||||||
@Param("limit") int limit,
|
@Param("limit") int limit,
|
||||||
@Param("offset") int offset);
|
@Param("offset") int offset);
|
||||||
|
|
||||||
default List<HistoryRecord> getRecent(HistoryType historyType, UserType userType, UUID uuid, int page) {
|
default List<HistoryRecord> getRecent(@NotNull HistoryType historyType, @NotNull UserType userType,
|
||||||
|
@NotNull UUID uuid, int page) {
|
||||||
return switch (historyType) {
|
return switch (historyType) {
|
||||||
case ALL -> getRecentAll(userType, uuid, page);
|
case ALL -> getRecentAll(userType, uuid, page);
|
||||||
case BAN -> getRecentBans(userType, uuid, page);
|
case BAN -> getRecentBans(userType, uuid, page);
|
||||||
|
|
@ -76,7 +80,8 @@ public interface UUIDHistoryMapper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecent(String tableName, UserType userType, UUID uuid, int page) {
|
private List<HistoryRecord> getRecent(@NotNull String tableName, @NotNull UserType userType,
|
||||||
|
@NotNull UUID uuid, int page) {
|
||||||
int offset = page * PAGE_SIZE;
|
int offset = page * PAGE_SIZE;
|
||||||
int limit = PAGE_SIZE;
|
int limit = PAGE_SIZE;
|
||||||
return switch (userType) {
|
return switch (userType) {
|
||||||
|
|
@ -85,7 +90,7 @@ public interface UUIDHistoryMapper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentAll(UserType userType, UUID uuid, int page) {
|
private List<HistoryRecord> getRecentAll(@NotNull UserType userType, @NotNull UUID uuid, int page) {
|
||||||
int offset = page * PAGE_SIZE;
|
int offset = page * PAGE_SIZE;
|
||||||
int limit = PAGE_SIZE;
|
int limit = PAGE_SIZE;
|
||||||
return switch (userType) {
|
return switch (userType) {
|
||||||
|
|
@ -96,23 +101,24 @@ public interface UUIDHistoryMapper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentBans(UserType userType, UUID uuid, int page) {
|
private @NotNull List<HistoryRecord> getRecentBans(@NotNull UserType userType, @NotNull UUID uuid, int page) {
|
||||||
return addType(getRecent("litebans_bans", userType, uuid, page), "ban");
|
return addType(getRecent("litebans_bans", userType, uuid, page), "ban");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentKicks(UserType userType, UUID uuid, int page) {
|
private @NotNull List<HistoryRecord> getRecentKicks(@NotNull UserType userType, @NotNull UUID uuid, int page) {
|
||||||
return addType(getRecent("litebans_kicks", userType, uuid, page), "kick");
|
return addType(getRecent("litebans_kicks", userType, uuid, page), "kick");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentMutes(UserType userType, UUID uuid, int page) {
|
private @NotNull List<HistoryRecord> getRecentMutes(@NotNull UserType userType, @NotNull UUID uuid, int page) {
|
||||||
return addType(getRecent("litebans_mutes", userType, uuid, page), "mute");
|
return addType(getRecent("litebans_mutes", userType, uuid, page), "mute");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecentWarns(UserType userType, UUID uuid, int page) {
|
private @NotNull List<HistoryRecord> getRecentWarns(@NotNull UserType userType, @NotNull UUID uuid, int page) {
|
||||||
return addType(getRecent("litebans_warnings", userType, uuid, page), "warn");
|
return addType(getRecent("litebans_warnings", userType, uuid, page), "warn");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> addType(List<HistoryRecord> historyRecords, String type) {
|
@Contract("_, _ -> param1")
|
||||||
|
private @NotNull List<HistoryRecord> addType(@NotNull List<HistoryRecord> historyRecords, @NotNull String type) {
|
||||||
historyRecords.forEach(historyRecord -> historyRecord.setType(type));
|
historyRecords.forEach(historyRecord -> historyRecord.setType(type));
|
||||||
return historyRecords;
|
return historyRecords;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ public class InitializeLiteBans {
|
||||||
configuration.addMapper(NameHistoryMapper.class);
|
configuration.addMapper(NameHistoryMapper.class);
|
||||||
configuration.addMapper(UUIDHistoryMapper.class);
|
configuration.addMapper(UUIDHistoryMapper.class);
|
||||||
configuration.addMapper(HistoryCountMapper.class);
|
configuration.addMapper(HistoryCountMapper.class);
|
||||||
|
configuration.addMapper(IdHistoryMapper.class);
|
||||||
}).join()
|
}).join()
|
||||||
.runQuery(sqlSession -> {
|
.runQuery(sqlSession -> {
|
||||||
createAllPunishmentsView(sqlSession);
|
createAllPunishmentsView(sqlSession);
|
||||||
|
|
@ -29,15 +30,15 @@ public class InitializeLiteBans {
|
||||||
private static void createAllPunishmentsView(SqlSession sqlSession) {
|
private static void createAllPunishmentsView(SqlSession sqlSession) {
|
||||||
String query = """
|
String query = """
|
||||||
CREATE VIEW IF NOT EXISTS all_punishments AS
|
CREATE VIEW IF NOT EXISTS all_punishments AS
|
||||||
SELECT uuid, reason, banned_by_uuid, banned_by_name, removed_by_name, time, until, removed_by_reason,
|
SELECT id, uuid, reason, banned_by_uuid, banned_by_name, removed_by_name, time, until, removed_by_reason,
|
||||||
'ban' as type
|
'ban' as type
|
||||||
FROM litebans_bans
|
FROM litebans_bans
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT uuid, reason, banned_by_uuid, banned_by_name, removed_by_name, time, until, removed_by_reason,
|
SELECT id, uuid, reason, banned_by_uuid, banned_by_name, removed_by_name, time, until, removed_by_reason,
|
||||||
'mute' as type
|
'mute' as type
|
||||||
FROM litebans_mutes
|
FROM litebans_mutes
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT uuid, reason, banned_by_uuid, banned_by_name, removed_by_name, time, until, removed_by_reason,
|
SELECT id, uuid, reason, banned_by_uuid, banned_by_name, removed_by_name, time, until, removed_by_reason,
|
||||||
'warn' as type
|
'warn' as type
|
||||||
FROM litebans_warnings
|
FROM litebans_warnings
|
||||||
ORDER BY time DESC;
|
ORDER BY time DESC;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ export const routes: Routes = [
|
||||||
path: 'bans',
|
path: 'bans',
|
||||||
loadComponent: () => import('./bans/bans.component').then(m => m.BansComponent)
|
loadComponent: () => import('./bans/bans.component').then(m => m.BansComponent)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'bans/:type/:id',
|
||||||
|
loadComponent: () => import('./bans/details/details.component').then(m => m.DetailsComponent)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'economy',
|
path: 'economy',
|
||||||
loadComponent: () => import('./economy/economy.component').then(m => m.EconomyComponent)
|
loadComponent: () => import('./economy/economy.component').then(m => m.EconomyComponent)
|
||||||
|
|
@ -52,6 +56,14 @@ export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'claiming',
|
path: 'claiming',
|
||||||
loadComponent: () => import('./claiming/claiming.component').then(m => m.ClaimingComponent)
|
loadComponent: () => import('./claiming/claiming.component').then(m => m.ClaimingComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'mypet',
|
||||||
|
loadComponent: () => import('./mypet/mypet.component').then(m => m.MypetComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'warps',
|
||||||
|
loadComponent: () => import('./warps/warps.component').then(m => m.WarpsComponent)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="historyTable">
|
<div class="historyTable">
|
||||||
<app-history [userType]="userType" [punishmentType]="punishmentType"
|
<app-history [userType]="userType" [punishmentType]="punishmentType"
|
||||||
[page]="page" [searchTerm]="finalSearchTerm" (pageChange)="updatePageSize($event)">
|
[page]="page" [searchTerm]="finalSearchTerm" (pageChange)="updatePageSize($event)"
|
||||||
|
(selectItem)="setSearch($event)">
|
||||||
</app-history>
|
</app-history>
|
||||||
</div>
|
</div>
|
||||||
<div class="changePageButtons">
|
<div class="changePageButtons">
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ 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';
|
import {catchError, map, Observable} from 'rxjs';
|
||||||
|
import {SearchParams} from './search-terms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bans',
|
selector: 'app-bans',
|
||||||
|
|
@ -210,4 +211,11 @@ export class BansComponent implements OnInit {
|
||||||
this.actualPage = this.page;
|
this.actualPage = this.page;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setSearch(searchParams: SearchParams) {
|
||||||
|
this.userType = searchParams.userType
|
||||||
|
this.searchTerm = searchParams.searchTerm
|
||||||
|
this.punishmentType = searchParams.punishmentType
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
frontend/src/app/bans/details/details.component.html
Normal file
62
frontend/src/app/bans/details/details.component.html
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'bans'" height="200px" background_image="/public/img/backgrounds/staff.png"
|
||||||
|
[overlay_gradient]="0.5">>
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Minecraft Punishments</h1>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<ng-container *ngIf="punishment === undefined">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="punishment">
|
||||||
|
<table [cellSpacing]="0">
|
||||||
|
<div>
|
||||||
|
<p>type: {{ this.historyFormat.getType(punishment) }}</p>
|
||||||
|
<p>is active: {{ this.historyFormat.isActive(punishment) }}</p>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Player</td>
|
||||||
|
<td>
|
||||||
|
<div class="playerContainer">
|
||||||
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(punishment.uuid)" width="25" height="25"
|
||||||
|
alt="{{punishment.username}}'s Minecraft skin">
|
||||||
|
<span class="username">{{ punishment.username }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Moderator</td>
|
||||||
|
<td>
|
||||||
|
<div class="playerContainer">
|
||||||
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(punishment.punishedByUuid)" width="25"
|
||||||
|
height="25"
|
||||||
|
alt="{{punishment.punishedBy}}'s Minecraft skin">
|
||||||
|
<span class="username">{{ punishment.punishedBy }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Reason</td>
|
||||||
|
<td>{{ punishment.reason | removeTrailingPeriod }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Date</td>
|
||||||
|
<td>{{ this.historyFormat.getPunishmentTime(punishment) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Expires</td>
|
||||||
|
<td>{{ this.historyFormat.getExpiredTime(punishment) }}</td>
|
||||||
|
</tr>
|
||||||
|
<ng-container *ngIf="punishment.removedBy !== undefined && punishment.removedBy.length > 0">
|
||||||
|
<tr>
|
||||||
|
<td>Un{{ this.historyFormat.getType(punishment).toLocaleLowerCase() }} reason</td>
|
||||||
|
<td>{{ punishment.removedReason == null ? 'No reason specified' : punishment.removedReason }}</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</div>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
23
frontend/src/app/bans/details/details.component.spec.ts
Normal file
23
frontend/src/app/bans/details/details.component.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DetailsComponent } from './details.component';
|
||||||
|
|
||||||
|
describe('DetailsComponent', () => {
|
||||||
|
let component: DetailsComponent;
|
||||||
|
let fixture: ComponentFixture<DetailsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DetailsComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DetailsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
69
frontend/src/app/bans/details/details.component.ts
Normal file
69
frontend/src/app/bans/details/details.component.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {HistoryService, PunishmentHistory} from '../../../api';
|
||||||
|
import {NgIf, NgOptimizedImage} from '@angular/common';
|
||||||
|
import {RemoveTrailingPeriodPipe} from '../../util/RemoveTrailingPeriodPipe';
|
||||||
|
import {HistoryFormatService} from '../history-format.service';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import {catchError, map} from 'rxjs';
|
||||||
|
import {HeaderComponent} from '../../header/header.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-details',
|
||||||
|
imports: [
|
||||||
|
NgIf,
|
||||||
|
NgOptimizedImage,
|
||||||
|
RemoveTrailingPeriodPipe,
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './details.component.html',
|
||||||
|
styleUrl: './details.component.scss'
|
||||||
|
})
|
||||||
|
export class DetailsComponent implements OnInit {
|
||||||
|
type: 'all' | 'ban' | 'mute' | 'kick' | 'warn' = 'all';
|
||||||
|
id: number = -1;
|
||||||
|
punishment: PunishmentHistory | undefined;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute,
|
||||||
|
private historyApi: HistoryService,
|
||||||
|
public historyFormat: HistoryFormatService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.paramMap.subscribe(params => {
|
||||||
|
switch (params.get('type')) {
|
||||||
|
case 'ban':
|
||||||
|
this.type = 'ban';
|
||||||
|
break;
|
||||||
|
case 'mute':
|
||||||
|
this.type = 'mute';
|
||||||
|
break;
|
||||||
|
case 'kick':
|
||||||
|
this.type = 'kick';
|
||||||
|
break;
|
||||||
|
case 'warn':
|
||||||
|
this.type = 'warn';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid type");
|
||||||
|
}
|
||||||
|
this.id = Number(params.get('id') || '-1');
|
||||||
|
this.loadPunishmentData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadPunishmentData() {
|
||||||
|
if (!this.type || this.type === 'all' || this.id < 0 || isNaN(this.id)) {
|
||||||
|
console.error("Invalid type or id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.historyApi.getHistoryById(this.type, this.id).pipe(
|
||||||
|
map(punishment => {
|
||||||
|
this.punishment = punishment;
|
||||||
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
console.error(err);
|
||||||
|
return [];
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
frontend/src/app/bans/history-format.service.spec.ts
Normal file
16
frontend/src/app/bans/history-format.service.spec.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HistoryFormatService } from './history-format.service';
|
||||||
|
|
||||||
|
describe('HistoryFormatService', () => {
|
||||||
|
let service: HistoryFormatService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(HistoryFormatService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
58
frontend/src/app/bans/history-format.service.ts
Normal file
58
frontend/src/app/bans/history-format.service.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {PunishmentHistory} from '../../api';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class HistoryFormatService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPunishmentTime(entry: PunishmentHistory) {
|
||||||
|
const date = new Date(entry.punishmentTime);
|
||||||
|
return date.toLocaleString(navigator.language, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public isActive(entry: PunishmentHistory): boolean {
|
||||||
|
return entry.expiryTime > Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(entry: PunishmentHistory): string {
|
||||||
|
return entry.type.charAt(0).toUpperCase() + entry.type.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getExpiredTime(entry: PunishmentHistory): string {
|
||||||
|
let suffix: string = '';
|
||||||
|
if (entry.removedBy !== null && entry.removedBy !== undefined) {
|
||||||
|
suffix = '(' + entry.removedBy + ')';
|
||||||
|
}
|
||||||
|
if (entry.expiryTime <= 0) {
|
||||||
|
return "Permanent " + this.getType(entry) + " " + suffix;
|
||||||
|
}
|
||||||
|
const date = new Date(entry.expiryTime);
|
||||||
|
return date.toLocaleString(navigator.language, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}) + " " + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAvatarUrl(entry: string): string {
|
||||||
|
let uuid = entry.replace('-', '');
|
||||||
|
if (uuid === 'C') {
|
||||||
|
uuid = "f78a4d8dd51b4b3998a3230f2de0c670"
|
||||||
|
}
|
||||||
|
return `https://crafatar.com/avatars/${uuid}?size=25&overlay`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,24 +19,32 @@
|
||||||
<div>
|
<div>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="historyPlayerRow" *ngFor="let entry of history">
|
<tr class="historyPlayerRow" *ngFor="let entry of history">
|
||||||
<td class="historyType">{{ getType(entry) }}</td>
|
<td class="historyType" (click)="showDetailedPunishment(entry)">
|
||||||
<td class="historyPlayer">
|
{{ this.historyFormat.getType(entry) }}
|
||||||
|
</td>
|
||||||
|
<td class="historyPlayer" (click)="setSearch(entry.type, entry.username, 'player')">
|
||||||
<div class="playerContainer">
|
<div class="playerContainer">
|
||||||
<img class="avatar" [ngSrc]="getAvatarUrl(entry.uuid)" width="25" height="25"
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(entry.uuid)" width="25" height="25"
|
||||||
alt="{{entry.username}}'s Minecraft skin">
|
alt="{{entry.username}}'s Minecraft skin">
|
||||||
<span class="username">{{ entry.username }}</span>
|
<span class="username">{{ entry.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="historyPlayer">
|
<td class="historyPlayer" (click)="setSearch(entry.type, entry.punishedBy, 'staff')">
|
||||||
<div class="playerContainer">
|
<div class="playerContainer">
|
||||||
<img class="avatar" [ngSrc]="getAvatarUrl(entry.punishedByUuid)" width="25" height="25"
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(entry.punishedByUuid)" width="25" height="25"
|
||||||
alt="{{entry.punishedBy}}'s Minecraft skin">
|
alt="{{entry.punishedBy}}'s Minecraft skin">
|
||||||
<span>{{ entry.punishedBy }}</span>
|
<span>{{ entry.punishedBy }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="historyReason">{{ entry.reason | removeTrailingPeriod }}</td>
|
<td class="historyReason" (click)="showDetailedPunishment(entry)">
|
||||||
<td class="historyDate">{{ getPunishmentTime(entry) }}</td>
|
{{ entry.reason | removeTrailingPeriod }}
|
||||||
<td class="historyDate">{{ getExpiredTime(entry) }}</td>
|
</td>
|
||||||
|
<td class="historyDate" (click)="showDetailedPunishment(entry)">
|
||||||
|
{{ this.historyFormat.getPunishmentTime(entry) }}
|
||||||
|
</td>
|
||||||
|
<td class="historyDate" (click)="showDetailedPunishment(entry)">
|
||||||
|
{{ this.historyFormat.getExpiredTime(entry) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ table tr td {
|
||||||
|
|
||||||
.historyPlayerRow:hover {
|
.historyPlayerRow:hover {
|
||||||
background-color: var(--history-table-row-color);
|
background-color: var(--history-table-row-color);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.historyTableHead {
|
.historyTableHead {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import {Component, EventEmitter, Input, OnChanges, OnInit, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, OnChanges, OnInit, Output} from '@angular/core';
|
||||||
import {BASE_PATH, HistoryService, PunishmentHistoryInner} from '../../../api';
|
import {BASE_PATH, HistoryService, PunishmentHistory} from '../../../api';
|
||||||
import {catchError, map, Observable, shareReplay} from 'rxjs';
|
import {catchError, map, Observable, shareReplay} from 'rxjs';
|
||||||
import {NgForOf, NgIf, NgOptimizedImage} from '@angular/common';
|
import {NgForOf, NgIf, NgOptimizedImage} from '@angular/common';
|
||||||
import {CookieService} from 'ngx-cookie-service';
|
import {CookieService} from 'ngx-cookie-service';
|
||||||
import {RemoveTrailingPeriodPipe} from '../../util/RemoveTrailingPeriodPipe';
|
import {RemoveTrailingPeriodPipe} from '../../util/RemoveTrailingPeriodPipe';
|
||||||
import {HttpErrorResponse} from '@angular/common/http';
|
import {HttpErrorResponse} from '@angular/common/http';
|
||||||
import {environment} from '../../../environments/environment';
|
import {environment} from '../../../environments/environment';
|
||||||
|
import {HistoryFormatService} from '../history-format.service';
|
||||||
|
import {SearchParams} from '../search-terms';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-history',
|
selector: 'app-history',
|
||||||
|
|
@ -30,12 +33,13 @@ export class HistoryComponent implements OnInit, OnChanges {
|
||||||
@Input() searchTerm: string = '';
|
@Input() searchTerm: string = '';
|
||||||
|
|
||||||
@Output() pageChange = new EventEmitter<number>();
|
@Output() pageChange = new EventEmitter<number>();
|
||||||
|
@Output() selectItem = new EventEmitter<SearchParams>();
|
||||||
|
|
||||||
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 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}$/;
|
||||||
|
|
||||||
public history: PunishmentHistoryInner[] = []
|
public history: PunishmentHistory[] = []
|
||||||
|
|
||||||
constructor(private historyApi: HistoryService) {
|
constructor(private historyApi: HistoryService, public historyFormat: HistoryFormatService, private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
|
|
@ -47,7 +51,7 @@ export class HistoryComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
private reloadHistory(): void {
|
private reloadHistory(): void {
|
||||||
let historyObservable: Observable<PunishmentHistoryInner[]>;
|
let historyObservable: Observable<PunishmentHistory[]>;
|
||||||
if (this.searchTerm.length === 0) {
|
if (this.searchTerm.length === 0) {
|
||||||
historyObservable = this.historyApi.getHistoryForAll(this.userType, this.punishmentType, this.page);
|
historyObservable = this.historyApi.getHistoryForAll(this.userType, this.punishmentType, this.page);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -89,42 +93,31 @@ export class HistoryComponent implements OnInit, OnChanges {
|
||||||
).subscribe();
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPunishmentTime(entry: PunishmentHistoryInner) {
|
setSearch(type: PunishmentHistory.TypeEnum, name: string, userType: 'player' | 'staff') {
|
||||||
const date = new Date(entry.punishmentTime);
|
let punishmentType: 'all' | 'ban' | 'mute' | 'kick' | 'warn' = 'all';
|
||||||
return date.toLocaleString(navigator.language, {
|
switch (type) {
|
||||||
year: 'numeric',
|
case 'ban':
|
||||||
month: 'short',
|
punishmentType = 'ban';
|
||||||
day: 'numeric',
|
break;
|
||||||
hour: '2-digit',
|
case 'mute':
|
||||||
minute: '2-digit',
|
punishmentType = 'mute';
|
||||||
hour12: false
|
break;
|
||||||
});
|
case 'kick':
|
||||||
}
|
punishmentType = 'kick';
|
||||||
|
break;
|
||||||
public getType(entry: PunishmentHistoryInner) {
|
case 'warn':
|
||||||
return entry.type.charAt(0).toUpperCase() + entry.type.slice(1);
|
punishmentType = 'warn';
|
||||||
}
|
break;
|
||||||
|
|
||||||
public getExpiredTime(entry: PunishmentHistoryInner) {
|
|
||||||
if (entry.expiryTime <= 0) {
|
|
||||||
return "Permanent " + this.getType(entry);
|
|
||||||
}
|
}
|
||||||
const date = new Date(entry.expiryTime);
|
let searchParams: SearchParams = {
|
||||||
return date.toLocaleString(navigator.language, {
|
userType: userType,
|
||||||
year: 'numeric',
|
punishmentType: punishmentType,
|
||||||
month: 'short',
|
searchTerm: name
|
||||||
day: 'numeric',
|
}
|
||||||
hour: '2-digit',
|
this.selectItem.emit(searchParams);
|
||||||
minute: '2-digit',
|
|
||||||
hour12: false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAvatarUrl(entry: string): string {
|
public showDetailedPunishment(entry: PunishmentHistory) {
|
||||||
let uuid = entry.replace('-', '');
|
this.router.navigate([`bans/${entry.type}/${entry.id}`]).then();
|
||||||
if (uuid === 'C') {
|
|
||||||
uuid = "f78a4d8dd51b4b3998a3230f2de0c670"
|
|
||||||
}
|
|
||||||
return `https://crafatar.com/avatars/${uuid}?size=25&overlay`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
frontend/src/app/bans/search-terms.ts
Normal file
5
frontend/src/app/bans/search-terms.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface SearchParams {
|
||||||
|
userType: 'player' | 'staff';
|
||||||
|
searchTerm: string;
|
||||||
|
punishmentType: 'all' | 'ban' | 'mute' | 'kick' | 'warn';
|
||||||
|
}
|
||||||
221
frontend/src/app/mypet/mypet.component.html
Normal file
221
frontend/src/app/mypet/mypet.component.html
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'mypet'" height="460px" background_image="/public/img/backgrounds/babywither.png"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>MyPet</h1>
|
||||||
|
<h2>Tame almost any mob in-game as your pet and train it to fight along your side, collect dropped items for you,
|
||||||
|
and more!</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Claim a Pet</h2>
|
||||||
|
<p>To claim a pet you will need to find the mob you want in the survival world and <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">kill it with a lead in your hand</span>. You must have a
|
||||||
|
lead in your hand when you kill the mob or it will die instead of becoming your pet.</p>
|
||||||
|
<p>New players can have up to 3 pets in storage and 1 active pet at a time. More pets can be stored by
|
||||||
|
ranking up. To claim an additional pet you must first store your current pet with <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/petstore</span>. Once you have stored your current
|
||||||
|
pet you can simply kill another mob to make it your pet. You will not be able to call it or interact with
|
||||||
|
your other pets while they are in storage. To interact with your MyPet you will need to make it your
|
||||||
|
active MyPet by doing <span style="font-family: 'opensans-bold', sans-serif;">/petswitch</span> and
|
||||||
|
selecting the one you want to use.</p>
|
||||||
|
<img ngSrc="/public/img/items/lead.png" alt="Minecraft lead/leash" style="width: 20%;" height="96"
|
||||||
|
width="96">
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Skilltrees and Levels</h2>
|
||||||
|
<p>Pets can be assigned to a skilltree with <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/pcst</span>, which allows it to level up and unlock new
|
||||||
|
skills. There are 8 skilltrees to choose from, but Mage, Tank, Marksman, and Warrior all require you to
|
||||||
|
start with "Fighter". Pets earn XP when they kill mobs, and also earn a small amount of XP any time you
|
||||||
|
kill a mob.</p>
|
||||||
|
<p><b>Note:</b> pets with inventories will drop it on player death.</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Utility</span> - Specializes in mining with
|
||||||
|
pickup and inventory skills + a Haste II beacon at level 150. (150 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Mount</span> - A rideable pet specializes in
|
||||||
|
speed and jump height + jump beacon at level 150, and inventory at level 200. (200 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Defender</span> - Specializes in defending
|
||||||
|
you, it absorbs damage dealt towards you. High HP + regen beacon at level 200. (200 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Fighter</span> - Starter rank for all other
|
||||||
|
fighter categories. Increases pet attack damage. (50 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Mage</span> - Specializes in specialty attacks
|
||||||
|
like fireballs and lightning. No melee abilities. (100 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Tank</span> - Specializes in defense with
|
||||||
|
lots of HP, redirecting damage, and sweeping attacks. Resistance beacon at level 100. (100 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Marksman</span> - Specializes in ranged
|
||||||
|
attacks with arrow and poison damage, no melee abilities + invisibility beacon at level 50. (100 levels)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Warrior</span> - An all-around class with no
|
||||||
|
speciality, has some skills from every other class + a strength beacon at level 100. (100 levels)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Equip Your Pet</h2>
|
||||||
|
<p>Some pets can hold items in their hands or wear armor. To equip a pet with something to hold or wear,
|
||||||
|
simply right click on your pet while shifting with the item in your hand. To remove the equipment right
|
||||||
|
click on your MyPet while shifting with shears in your hand.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Naming Your Pet</h2>
|
||||||
|
<p>You can name your pet with <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/petname <name></span>. Please note that
|
||||||
|
inappropriate names (anything that wouldn't be allowed to be said in chat) are not allowed.</p>
|
||||||
|
<p>Duke members and above can change the color of their pet's name. To get a list of all the color codes
|
||||||
|
please do <span style="font-family: 'opensans-bold', sans-serif;">/colors</span> in-game.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Pet Behavior</h2>
|
||||||
|
<p>You can change the way your pet attacks by changing its behavior. Different behaviors are unlocked as
|
||||||
|
your pet levels up. The available behaviors are listed below.</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Friendly:</span> Won't fight, even if it's
|
||||||
|
attacked by anything
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Normal:</span> Acts like a normal wolf</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Aggressive:</span> Attacks everything within
|
||||||
|
15 blocks of the owner
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Farm:</span> Attacks every hostile mob within
|
||||||
|
15 blocks of the owner
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Raid:</span> Like normal, but the MyPet won't
|
||||||
|
attack players and their pets
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Duel</span> Attacks other pets with active
|
||||||
|
duel behavior within a 5 block radius
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Feed Your Pet</h2>
|
||||||
|
<p>You must feed your pet to keep its health up or it will die from hunger! If your pet is hungry it will
|
||||||
|
also be weaker and will deal less damage when attacking.</p>
|
||||||
|
<p>Each pet has a special food that it must be fed. You can see which food is right for your pet by doing
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">/petinfo</span>.</p>
|
||||||
|
<div class="inlineIcons">
|
||||||
|
<img ngSrc="/public/img/items/potato.png" alt="Minecraft potato" height="81" width="77">
|
||||||
|
<img ngSrc="/public/img/items/carrot.png" alt="Minecraft carrot" height="81" width="89">
|
||||||
|
<img ngSrc="/public/img/items/steak.png" alt="Minecraft steak" height="81" width="88">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>How to Hide/Disable a MyPet</h2>
|
||||||
|
<p>There are two ways to disable a pet, and they work very differently. The first, <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/petsendaway</span>, will hide your active MyPet until
|
||||||
|
you un-hide it with <span style="font-family: 'opensans-bold', sans-serif;">/petcall</span>. A pet that
|
||||||
|
has been hidden is still considered your "active MyPet", it just isn't visible right now. It also only
|
||||||
|
hides your pet during your current session.</p>
|
||||||
|
<p>The second way to disable a pet is to put it into storage with <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/petstore</span>. Pets that are in storage are
|
||||||
|
completely deactivated until you use <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/petswitch</span> to choose one you want to use. If
|
||||||
|
all of your pets are in storage, the server will tell you that you do not have a MyPet. This is also how
|
||||||
|
you can claim additional MyPets - by completely deactivating your current one first.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Blocked Mob Types</h2>
|
||||||
|
<p>The following mobs are not allowed to be claimed as a pet: the enderdragon, villagers and villager-like
|
||||||
|
mobs, shulkers, ghasts, and elder guardians. If you kill any of these mobs it will die and will NOT become
|
||||||
|
your pet.</p>
|
||||||
|
<p>Furthermore, all horses (including donkeys and mules) are currently disabled as pets due to them being
|
||||||
|
buggy.</p>
|
||||||
|
<div class="inlineIcons">
|
||||||
|
<img ngSrc="/public/img/items/enderdragon.png"
|
||||||
|
style="padding: 25px 25px;"
|
||||||
|
alt="Minecraft enderdragon mob"
|
||||||
|
height="100" width="100">
|
||||||
|
<img ngSrc="/public/img/items/villager.png"
|
||||||
|
style="padding: 25px 25px;"
|
||||||
|
alt="Minecraft villager mob"
|
||||||
|
height="100" width="100">
|
||||||
|
<img ngSrc="/public/img/items/zombievillager.png"
|
||||||
|
style="padding: 25px 25px;"
|
||||||
|
alt="Minecraft zombie villager mob"
|
||||||
|
height="100" width="100">
|
||||||
|
<img ngSrc="/public/img/items/shulker.png"
|
||||||
|
style="padding: 25px 25px;"
|
||||||
|
alt="Minecraft shulker mob"
|
||||||
|
height="100" width="100">
|
||||||
|
<img ngSrc="/public/img/items/ghast.png"
|
||||||
|
style="padding: 25px 25px;"
|
||||||
|
alt="Minecraft ghast mob"
|
||||||
|
height="100" width="100">
|
||||||
|
<img ngSrc="/public/img/items/elderguardian.png"
|
||||||
|
style="padding: 25px 25px;"
|
||||||
|
alt="Minecraft elder guardian mob"
|
||||||
|
height="100" width="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Disclaimer</h2>
|
||||||
|
<p>When you die with your MyPet out, the MyPet will drop all of the items in its inventory. This means that
|
||||||
|
if your MyPet is in a dangerous area when you die, your items can be lost. And if you die in pvp the items
|
||||||
|
won't be protected. If your MyPet dies on it’s own, it will not drop it’s inventory.</p>
|
||||||
|
<p>Some skill trees require a base skill to be able to select them. The fighter skill tree is a base skill
|
||||||
|
and will not level beyond 50. When you reach level 50, you will need to switch to one of the secondary
|
||||||
|
skill trees with <span style="font-family: 'opensans-bold', sans-serif;">/pcst.</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful Commands</h2>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petcall -</span> Make your pet come to you if
|
||||||
|
it vanished
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petsendaway -</span> Make your pet vanish
|
||||||
|
temporarily
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petrelease <name>-</span> Release
|
||||||
|
your pet forever
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petinfo -</span> Check your pet's health and
|
||||||
|
food as well as other stats
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pcst -</span> Choose a skilltree for your
|
||||||
|
pet.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petskill -</span> Check your pet's skills and
|
||||||
|
strengths
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petname <name>-</span> Change the name
|
||||||
|
of your pet (see Naming Your Pet above)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petinventory -</span> Open your pet's
|
||||||
|
inventory to see what it has picked up
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petpickup -</span> Toggle whether your pet
|
||||||
|
will pick up items or not
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petbehavior -</span> Change the behavior
|
||||||
|
class of your pet (see Pet Behavior above)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petbeacon -</span> Turn your pet into a
|
||||||
|
walking beacon to give you strength, speed, and other power-ups
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petswitch -</span> Allows you to switch
|
||||||
|
between your MyPets
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petstore -</span> Store your current pet to
|
||||||
|
claim another
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
15
frontend/src/app/mypet/mypet.component.scss
Normal file
15
frontend/src/app/mypet/mypet.component.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
main ul {
|
||||||
|
font-family: opensans, sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inlineIcons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
23
frontend/src/app/mypet/mypet.component.spec.ts
Normal file
23
frontend/src/app/mypet/mypet.component.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MypetComponent } from './mypet.component';
|
||||||
|
|
||||||
|
describe('MypetComponent', () => {
|
||||||
|
let component: MypetComponent;
|
||||||
|
let fixture: ComponentFixture<MypetComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [MypetComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MypetComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
16
frontend/src/app/mypet/mypet.component.ts
Normal file
16
frontend/src/app/mypet/mypet.component.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from '../header/header.component';
|
||||||
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-mypet',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
NgOptimizedImage
|
||||||
|
],
|
||||||
|
templateUrl: './mypet.component.html',
|
||||||
|
styleUrl: './mypet.component.scss'
|
||||||
|
})
|
||||||
|
export class MypetComponent {
|
||||||
|
|
||||||
|
}
|
||||||
223
frontend/src/app/warps/warps.component.html
Normal file
223
frontend/src/app/warps/warps.component.html
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'economy'" height="460px" background_image="/public/img/backgrounds/path.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Player Warps</h1>
|
||||||
|
<h2>Set up your own public warp to let everyone visit your town, shop, and more!</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>What are Warps?</h2>
|
||||||
|
<p>Warps allow everyone to teleport to a specific place within the world. They work just like homes
|
||||||
|
(/sethome), but everyone has access to the warps. We are very excited to have our own custom warp system
|
||||||
|
that allows players to easily apply for their own warp wherever they want and maintain their warp (the
|
||||||
|
name, description, and icon) on their own! This lets you easily promote your town, shop, farm, or just
|
||||||
|
about anything else.</p>
|
||||||
|
<p>All warps are separated into categories so it's easy to find the category of warp you're looking for.</p>
|
||||||
|
<img ngSrc="/public/img/random/warpgui.png"
|
||||||
|
alt="In-game warp GUI"
|
||||||
|
style="width: 80%; padding-bottom: 15px;"
|
||||||
|
height="232" width="356">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Applying for a Warp</h2>
|
||||||
|
<p>Once you've built something that you want to share, and made sure that it meets the "Warp Requirements",
|
||||||
|
you can apply for a warp in-game! Applying is easy, just do <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/warps apply</span> and follow the prompts.</p>
|
||||||
|
<p>The application process will ask you for a name, description, icon, category, and location for your warp.
|
||||||
|
Before beginning the application process, be sure you are standing where you want the warp to be! Make
|
||||||
|
sure you are also facing the direction you want players to face when they teleport to your warp. Don't
|
||||||
|
look at the ground! Your warp name must be under 30 characters and your description must be under 120. Do
|
||||||
|
not use any color codes. When it asks for the item you want to have as an icon, simply hold the desired
|
||||||
|
item in your hand.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">Attention</span>: Warps cost $25000. If your
|
||||||
|
application is denied, you will be refunded $15000. To continue, type continue, or to cancel, type cancel.
|
||||||
|
However, if your warp is approved, but you fail to maintain it, your warp will be deleted without a
|
||||||
|
refund!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="columnSection" style="padding-top: 0;">
|
||||||
|
<div class="columnParagraph" style="padding-left: 15px;">
|
||||||
|
<h2>Warp Requirements</h2>
|
||||||
|
<p>You need to be the owner of the claim your warp is placed in. It should look good, and be as finished as
|
||||||
|
your warp type allows you to have it. Safety is an important aspect as well, visitors should not be
|
||||||
|
accidently dying by falling in dangerous areas or having mobs spawn on them. Phantoms are an exception to
|
||||||
|
this rule as you can't easily prevent those spawns. Your warp should be in one claim*, and divided with
|
||||||
|
subclaims if you need to allow different levels of trust in different areas (house plots in towns, shop
|
||||||
|
plots in malls, etc).</p>
|
||||||
|
<p><sub>*If a warp cannot be done in one claim due to surrounding claims, you are allowed to use up to 3
|
||||||
|
separate claims to claim everything. If you can reduce the total size of your claim by at least roughly 30%
|
||||||
|
by using an extra claim, you may do so for up to a total of 3 claims.</sub></p>
|
||||||
|
<p>It should be easy for players to find where they want to go. You can accomplish this through signs or by
|
||||||
|
designing it in such a way that players can see their destination when they warp in. Your warp's claim
|
||||||
|
border should be 500 blocks away from any other warp's claim border, and two warps can not lead to the same
|
||||||
|
area. We encourage making it possible for players to leave the warp, either by walking or elytra use, so
|
||||||
|
that they can use it as a starting point for exploring as well.</p>
|
||||||
|
<p>If you have any farms in the area of your warp that players don't benefit from through your warp it should
|
||||||
|
not be possible for players to load those farms during regular use. We don't want players being used as
|
||||||
|
chunkloaders.</p>
|
||||||
|
<p>It's important to make sure your warp is as lag friendly as possible. Our rules on lag can be found here on
|
||||||
|
the <a [routerLink]="['/lag']">lag</a> page. If we find your warp creates more lag than necessary for its
|
||||||
|
function
|
||||||
|
we will deny the application.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h3>Towns</h3>
|
||||||
|
<span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">Any warp that is designed to house other players</span>
|
||||||
|
<ul>
|
||||||
|
<li>It should be clear how to navigate the town and how to get a plot</li>
|
||||||
|
<li>You need to be active enough to regularly assign players new plots</li>
|
||||||
|
<li>Your town must have a community that actively participates in your town by living/building there or
|
||||||
|
using your towns features both before applying and while it's a warp (Minimum 3 active players other
|
||||||
|
than you)
|
||||||
|
</li>
|
||||||
|
<li>Players should be able to leave the town area in order to use the warp as a travel hub</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h3>Shops</h3>
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">Any warp that sells/trades/buys items using shops. This also includes malls.</span>
|
||||||
|
<ul>
|
||||||
|
<li>It should be clear where to go to find the items that are being sold from the warp spawn point</li>
|
||||||
|
<li>Your shops should sell items players want and aren’t readily available elsewhere</li>
|
||||||
|
<li>Your prices should be competitive with spawn and other warps</li>
|
||||||
|
<li>You are responsible for keeping stock up. Staff will regularly check random shops within your warp, if
|
||||||
|
they find understocked shops they will issue warnings for it and eventually remove it. This counts for
|
||||||
|
mall owners as well!
|
||||||
|
</li>
|
||||||
|
<li>Players should be able to leave the area in order to use the warp as a travel hub</li>
|
||||||
|
</ul>
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">General stock requirements</span>
|
||||||
|
<ul>
|
||||||
|
<li>Easily obtainable items such as concrete: full chest</li>
|
||||||
|
<li>Hard to obtain, common items such as diamonds: 1 stack</li>
|
||||||
|
<li>Hard to obtain, uncommon items such as netherite/beacons: 16</li>
|
||||||
|
<li>Maps: 16 for 1x1, 8 for anything over 1x1 up to 2x2, and 4 for anything over 2x2</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Warp Notes</h2>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">-</span> If a new warp application has
|
||||||
|
improvements or additional, useful features that a current warp lacks, the current warp may be replaced
|
||||||
|
with the new one. For example, if there already is a single-spawner spider farm that offers drops at a
|
||||||
|
certain price, and a warp application is submitted for a spider farm with multiple spawners and offers
|
||||||
|
free drops, the new warp is likely to be favored.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">-</span> Warps that are malls (primarily focused
|
||||||
|
on having many shop plots for many players) are limited to 2.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">-</span> Smaller shop warps are limited to 1 shop
|
||||||
|
per type. For example, only 1 warp for redstone-related shops.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">-</span> In order to always keep our warps fair
|
||||||
|
and updated, we will automatically remove a warp if the warp owner has been offline for 30+ days. We need
|
||||||
|
to be able to contact our warp owners in a timely manner incase any issues with the warp happen to pop up.
|
||||||
|
If we notice that a player is fairly inactive and another warp application comes in for a similar warp,
|
||||||
|
the application will be more likely to be accepted if the warp meets all of our requirements. If you plan
|
||||||
|
on being gone for an extended period of time, 14+ days, you can let a staff member know and they will note
|
||||||
|
it down and try to work with you.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">-</span> The staff team does weekly warp checks
|
||||||
|
to make sure our warps continue to stay up to meet our requirements. They will mail a warp owner if they
|
||||||
|
notice an issue (low stock, sudden lighting issue, farm isn't working, etc.), and we expect all issues to
|
||||||
|
be fixed within a week of receiving that mail. If an issue goes unfixed, we will remove the warp due to
|
||||||
|
the lack of upkeep. If the issue is something you need help fixing or you're unable to work on it during
|
||||||
|
the week, you can reach out to any member of the staff team and they will work with you!</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful Commands</h2>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/warps -</span> Open a GUI showing all warps
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/warps apply -</span> Apply for your own warp
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h3>Farms</h3>
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">Any warps that give XP</span>
|
||||||
|
<ul>
|
||||||
|
<li>It should be easy to find where to go and how to use the farm</li>
|
||||||
|
<li>Players should have access to all of the drops either through shops with competitive prices, or for
|
||||||
|
free
|
||||||
|
</li>
|
||||||
|
<li>XP farms should not attempt to bypass our anti lag systems</li>
|
||||||
|
<li>Kill chambers should be 1x1 or 1x2, exceptions to this rule are listed below</li>
|
||||||
|
<li>Amount of farms allowed as warps:
|
||||||
|
<ul>
|
||||||
|
<li>Skeleton/spider - 1 each</li>
|
||||||
|
<li>Zombie/drowned - 1</li>
|
||||||
|
<li>Bad Omen - 1</li>
|
||||||
|
<li>Raid - 2</li>
|
||||||
|
<li>Guardian - 2</li>
|
||||||
|
<li>Endermen - 2</li>
|
||||||
|
<li>Creeper - 2</li>
|
||||||
|
<li>Shulker - 2</li>
|
||||||
|
<li>Wither skeleton - 2</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<p>Exceptions can be made for this rule if the farms have different, unique designs (such as a one-player
|
||||||
|
vs two-player farm). Head Staff will approve these exceptions on a case-by-case basis</p>
|
||||||
|
</ul>
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">Kill chamber size exceptions</span>
|
||||||
|
<ul>
|
||||||
|
<li>Magma kill chamber may be up to 3x3</li>
|
||||||
|
<li>Hoglin kill chamber may be up to 2x2</li>
|
||||||
|
<li>Ravagers during pillager raids must be killed automatically and the kill chamber should still be 2x1
|
||||||
|
for the rest of the mobs
|
||||||
|
</li>
|
||||||
|
<li>Enderman kill chamber may be up to 3x3</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h3>Other</h3>
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">Any warp that doesn't fit in the other categories can go in here</span>
|
||||||
|
<ul>
|
||||||
|
<li>Warps designed for player events need to look aesthetically pleasing and accommodate a large amount of
|
||||||
|
players
|
||||||
|
</li>
|
||||||
|
<li>Casino warps should, at a minimum, display or list the prizes that can be won from each game
|
||||||
|
<ul>
|
||||||
|
<li style="padding-top: 2px;">If the game allows you to win crate items, listing the rarity of prizes
|
||||||
|
that can be won is acceptable
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Casino warps should have a disclaimer posted visibly that tells players that they can lose money and
|
||||||
|
should expect to lose money playing any gambling machines
|
||||||
|
</li>
|
||||||
|
<li>Portal warps need to be the only one of its kind</li>
|
||||||
|
<li>Villager trading areas have their villagers on no AI blocks and be named public for public
|
||||||
|
accessibility
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Maintaining a Warp</h2>
|
||||||
|
<p>We've made it easy for you to maintain your warp on your own. As the warp owner you can change the name,
|
||||||
|
description, and icon anytime. There is no charge for modifying a warp. To access the GUI to manage your
|
||||||
|
warp, just do <span style="font-family: 'opensans-bold', sans-serif;">/warps</span> and click on the chest
|
||||||
|
labeled "<span style="font-family: 'opensans-bold', sans-serif;">My Warps</span>" in the bottom left
|
||||||
|
corner.</p>
|
||||||
|
<img ngSrc="/public/img/random/editwarpgui.png"
|
||||||
|
alt="In-game warp edit GUI"
|
||||||
|
style="width: 80%;"
|
||||||
|
width="384" height="170">
|
||||||
|
<p>Maintaining a warp also involves making sure it looks nice, shops are well stocked, and, if it's a town,
|
||||||
|
open plots are always available for residents to move in. Make sure you keep up on maintaining your warp
|
||||||
|
or it could be deleted! If a warp is deleted by a staff member you will not receive a refund for the
|
||||||
|
creation cost.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
14
frontend/src/app/warps/warps.component.scss
Normal file
14
frontend/src/app/warps/warps.component.scss
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
main ul {
|
||||||
|
font-family: opensans, sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.columnParagraph > span {
|
||||||
|
color: var(--font-color);
|
||||||
|
transition: 0.5s ease;
|
||||||
|
}
|
||||||
23
frontend/src/app/warps/warps.component.spec.ts
Normal file
23
frontend/src/app/warps/warps.component.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { WarpsComponent } from './warps.component';
|
||||||
|
|
||||||
|
describe('WarpsComponent', () => {
|
||||||
|
let component: WarpsComponent;
|
||||||
|
let fixture: ComponentFixture<WarpsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [WarpsComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(WarpsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
18
frontend/src/app/warps/warps.component.ts
Normal file
18
frontend/src/app/warps/warps.component.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from '../header/header.component';
|
||||||
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-warps',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
NgOptimizedImage,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './warps.component.html',
|
||||||
|
styleUrl: './warps.component.scss'
|
||||||
|
})
|
||||||
|
export class WarpsComponent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ paths:
|
||||||
$ref: './schemas/bans/bans.yml#/getTotalResultsForUuidSearch'
|
$ref: './schemas/bans/bans.yml#/getTotalResultsForUuidSearch'
|
||||||
/history/{userType}/search-results/user/{type}/{user}:
|
/history/{userType}/search-results/user/{type}/{user}:
|
||||||
$ref: './schemas/bans/bans.yml#/getTotalResultsForUserSearch'
|
$ref: './schemas/bans/bans.yml#/getTotalResultsForUserSearch'
|
||||||
|
/history/single/{type}/{id}:
|
||||||
|
$ref: './schemas/bans/bans.yml#/getHistoryById'
|
||||||
/history/total:
|
/history/total:
|
||||||
$ref: './schemas/bans/bans.yml#/getTotalPunishments'
|
$ref: './schemas/bans/bans.yml#/getTotalPunishments'
|
||||||
/appeal/update-mail:
|
/appeal/update-mail:
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ getHistoryForUsers:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PunishmentHistory'
|
$ref: '#/components/schemas/PunishmentHistoryList'
|
||||||
default:
|
default:
|
||||||
description: Unexpected error
|
description: Unexpected error
|
||||||
content:
|
content:
|
||||||
|
|
@ -70,7 +70,7 @@ getHistoryForAll:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PunishmentHistory'
|
$ref: '#/components/schemas/PunishmentHistoryList'
|
||||||
default:
|
default:
|
||||||
description: Unexpected error
|
description: Unexpected error
|
||||||
content:
|
content:
|
||||||
|
|
@ -97,7 +97,7 @@ getHistoryForUuid:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PunishmentHistory'
|
$ref: '#/components/schemas/PunishmentHistoryList'
|
||||||
default:
|
default:
|
||||||
description: Unexpected error
|
description: Unexpected error
|
||||||
content:
|
content:
|
||||||
|
|
@ -172,6 +172,29 @@ getTotalResultsForUuidSearch:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
||||||
|
getHistoryById:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- history
|
||||||
|
summary: Gets history for specified id
|
||||||
|
description: Retrieves a specific history record
|
||||||
|
operationId: getHistoryById
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/HistoryType'
|
||||||
|
- $ref: '#/components/parameters/Id'
|
||||||
|
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:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
HistoryType:
|
HistoryType:
|
||||||
|
|
@ -211,57 +234,70 @@ components:
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
description: The page that should be retrieved
|
description: The page that should be retrieved
|
||||||
|
Id:
|
||||||
|
name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: The id of the punishment that should be retrieved
|
||||||
schemas:
|
schemas:
|
||||||
SearchResults:
|
SearchResults:
|
||||||
type: integer
|
type: integer
|
||||||
description: A number representing the total count of results for the search query
|
description: A number representing the total count of results for the search query
|
||||||
PunishmentHistory:
|
PunishmentHistoryList:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
$ref: '#/components/schemas/PunishmentHistory'
|
||||||
required:
|
PunishmentHistory:
|
||||||
- username
|
type: object
|
||||||
- uuid
|
required:
|
||||||
- reason
|
- username
|
||||||
- type
|
- uuid
|
||||||
- punishmentTime
|
- reason
|
||||||
- expiryTime
|
- type
|
||||||
- punishedBy
|
- punishmentTime
|
||||||
- punishedByUuid
|
- expiryTime
|
||||||
properties:
|
- punishedBy
|
||||||
username:
|
- punishedByUuid
|
||||||
type: string
|
- id
|
||||||
description: The username of the user
|
properties:
|
||||||
uuid:
|
username:
|
||||||
type: string
|
type: string
|
||||||
description: The UUID of the user
|
description: The username of the user
|
||||||
reason:
|
uuid:
|
||||||
type: string
|
type: string
|
||||||
description: The reason for the punishment
|
description: The UUID of the user
|
||||||
type:
|
reason:
|
||||||
type: string
|
type: string
|
||||||
description: The type of punishment
|
description: The reason for the punishment
|
||||||
enum: [ ban, mute, kick, warn ]
|
type:
|
||||||
punishmentTime:
|
type: string
|
||||||
type: integer
|
description: The type of punishment
|
||||||
format: int64
|
enum: [ ban, mute, kick, warn ]
|
||||||
description: The time when the punishment was given
|
punishmentTime:
|
||||||
expiryTime:
|
type: integer
|
||||||
type: integer
|
format: int64
|
||||||
format: int64
|
description: The time when the punishment was given
|
||||||
description: The time when the punishment expires
|
expiryTime:
|
||||||
punishedBy:
|
type: integer
|
||||||
type: string
|
format: int64
|
||||||
description: The username of the punishment issuer
|
description: The time when the punishment expires
|
||||||
punishedByUuid:
|
punishedBy:
|
||||||
type: string
|
type: string
|
||||||
description: The UUID of the punishment issuer
|
description: The username of the punishment issuer
|
||||||
removedBy:
|
punishedByUuid:
|
||||||
type: string
|
type: string
|
||||||
description: The name of the staff member who removed the punishment
|
description: The UUID of the punishment issuer
|
||||||
removedReason:
|
removedBy:
|
||||||
type: string
|
type: string
|
||||||
description: The reason why the punishment was removed
|
description: The name of the staff member who removed the punishment
|
||||||
|
removedReason:
|
||||||
|
type: string
|
||||||
|
description: The reason why the punishment was removed
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
description: Id of the punishment
|
||||||
Player:
|
Player:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user