basically all curtis code from 5 years ago
This commit is contained in:
parent
c103e3644f
commit
9abe945f21
18
API/.gitignore
vendored
Normal file
18
API/.gitignore
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# IntelliJ
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
.idea\*.xml
|
||||||
|
|
||||||
|
# Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
log/
|
||||||
|
target/
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
# Java
|
||||||
|
*.jar
|
||||||
|
config.json
|
||||||
|
disguisePresets.json
|
97
API/pom.xml
Normal file
97
API/pom.xml
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.7.3</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>org.hcrival</groupId>
|
||||||
|
<artifactId>api</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>api</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>23.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongo-java-driver</artifactId>
|
||||||
|
<version>LATEST</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.mail</groupId>
|
||||||
|
<artifactId>mail</artifactId>
|
||||||
|
<version>1.5.0-b01</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.warrenstrange</groupId>
|
||||||
|
<artifactId>googleauth</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.5</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.24</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>LATEST</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>redis.clients</groupId>
|
||||||
|
<artifactId>jedis</artifactId>
|
||||||
|
<version>2.8.1</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
104
API/src/main/java/org/hcrival/api/InvictusAPI.java
Normal file
104
API/src/main/java/org/hcrival/api/InvictusAPI.java
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package org.hcrival.api;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.hcrival.api.banphrase.BanphraseService;
|
||||||
|
import org.hcrival.api.config.MainConfig;
|
||||||
|
import org.hcrival.api.discord.DiscordService;
|
||||||
|
import org.hcrival.api.disguise.DisguiseService;
|
||||||
|
import org.hcrival.api.forum.ForumService;
|
||||||
|
import org.hcrival.api.forum.account.AccountService;
|
||||||
|
import org.hcrival.api.forum.category.CategoryService;
|
||||||
|
import org.hcrival.api.forum.forum.ForumModel;
|
||||||
|
import org.hcrival.api.forum.forum.ForumModelService;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.profile.ProfileController;
|
||||||
|
import org.hcrival.api.profile.ProfileService;
|
||||||
|
import org.hcrival.api.profile.grant.GrantService;
|
||||||
|
import org.hcrival.api.profile.note.NoteService;
|
||||||
|
import org.hcrival.api.punishment.PunishmentService;
|
||||||
|
import org.hcrival.api.rank.RankService;
|
||||||
|
import org.hcrival.api.redis.RedisService;
|
||||||
|
import org.hcrival.api.tag.TagService;
|
||||||
|
import org.hcrival.api.totp.TotpService;
|
||||||
|
import org.hcrival.api.util.configuration.ConfigurationService;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@Getter
|
||||||
|
public class InvictusAPI {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static InvictusAPI instance;
|
||||||
|
|
||||||
|
private final ConfigurationService configurationService = new JsonConfigurationService();
|
||||||
|
private final MainConfig mainConfig = configurationService.loadConfiguration(MainConfig.class, new File("./config.json"));
|
||||||
|
|
||||||
|
private final RedisService redisService;
|
||||||
|
private final MongoService mongoService;
|
||||||
|
|
||||||
|
private final TagService tagService;
|
||||||
|
private final RankService rankService;
|
||||||
|
private final PunishmentService punishmentService;
|
||||||
|
private final GrantService grantService;
|
||||||
|
private final NoteService noteService;
|
||||||
|
private final DisguiseService disguiseService;
|
||||||
|
private final BanphraseService banphraseService;
|
||||||
|
private final ProfileService profileService;
|
||||||
|
private final DiscordService discordService;
|
||||||
|
private final TotpService totpService;
|
||||||
|
|
||||||
|
// website
|
||||||
|
private final ForumService forumService;
|
||||||
|
|
||||||
|
private final long startedAt;
|
||||||
|
|
||||||
|
public InvictusAPI() {
|
||||||
|
InvictusAPI.instance = this;
|
||||||
|
|
||||||
|
this.redisService = new RedisService(mainConfig.getRedisConfig(), "invictus");
|
||||||
|
this.mongoService = new MongoService(this);
|
||||||
|
mongoService.connect();
|
||||||
|
|
||||||
|
this.discordService = new DiscordService(this);
|
||||||
|
this.profileService = new ProfileService(this);
|
||||||
|
|
||||||
|
this.tagService = new TagService(this);
|
||||||
|
tagService.loadTags();
|
||||||
|
|
||||||
|
this.rankService = new RankService(this);
|
||||||
|
rankService.loadRanks();
|
||||||
|
|
||||||
|
this.punishmentService = new PunishmentService(this);
|
||||||
|
this.grantService = new GrantService(this);
|
||||||
|
this.noteService = new NoteService(this);
|
||||||
|
|
||||||
|
this.disguiseService = new DisguiseService(this);
|
||||||
|
disguiseService.loadPresets();
|
||||||
|
|
||||||
|
this.banphraseService = new BanphraseService(this);
|
||||||
|
banphraseService.loadBanphrases();
|
||||||
|
|
||||||
|
this.totpService = new TotpService(this);
|
||||||
|
this.forumService = new ForumService(this);
|
||||||
|
|
||||||
|
this.startedAt = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveMainConfig() {
|
||||||
|
try {
|
||||||
|
configurationService.saveConfiguration(mainConfig, new File("./config.json"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(InvictusAPI.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
137
API/src/main/java/org/hcrival/api/MainController.java
Normal file
137
API/src/main/java/org/hcrival/api/MainController.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package org.hcrival.api;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.TimeUtils;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
public class MainController {
|
||||||
|
|
||||||
|
private final InvictusAPI api = InvictusAPI.getInstance();
|
||||||
|
|
||||||
|
@GetMapping(path = "/istheapiworking")
|
||||||
|
public ResponseEntity<JsonElement> test() {
|
||||||
|
return new ResponseEntity<>(new JsonBuilder().add("message", "yes!").build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/stats/cache")
|
||||||
|
public ResponseEntity<JsonElement> statsCache() {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
JsonBuilder general = new JsonBuilder();
|
||||||
|
general.add("Loaded Ranks", api.getRankService().getCache().size());
|
||||||
|
general.add("Loaded Tags", api.getTagService().getCache().size());
|
||||||
|
general.add("Loaded Banphrases", api.getBanphraseService().getCache().size());
|
||||||
|
general.add("Cached Profiles", api.getProfileService().getCache().size());
|
||||||
|
general.add("Cached Punishments", api.getPunishmentService().getCache().size());
|
||||||
|
general.add("Cached Punishment Players", api.getPunishmentService().getPlayerCache().size());
|
||||||
|
general.add("Cached Grants", api.getGrantService().getCache().size());
|
||||||
|
general.add("Cached Grant Players", api.getGrantService().getPlayerCache().size());
|
||||||
|
general.add("Cached Disguise-Data", api.getDisguiseService().getCache().size());
|
||||||
|
general.add("Cached Discord-Data", api.getDiscordService().getUuidCache().size());
|
||||||
|
|
||||||
|
JsonBuilder forum = new JsonBuilder();
|
||||||
|
forum.add("Loaded Categories", api.getForumService().getCategoryService().getCache().size());
|
||||||
|
forum.add("Loaded Forums", api.getForumService().getForumModelService().getCache().size());
|
||||||
|
forum.add("Cached Accounts", api.getForumService().getAccountService().getCache().size());
|
||||||
|
forum.add("Cached Threads", api.getForumService().getThreadService().getCache().size());
|
||||||
|
forum.add("Cached Tickets", api.getForumService().getTicketService().getCache().size());
|
||||||
|
|
||||||
|
response.add("General", general.build());
|
||||||
|
response.add("Forums", forum.build());
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/stats/api")
|
||||||
|
public ResponseEntity<JsonElement> statsApi() {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
response.add("Uptime", TimeUtils.formatTimeShort(System.currentTimeMillis() - api.getStartedAt()));
|
||||||
|
// response.add("Handled Requests", master.getApi().getHandledRequests());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/stats/master")
|
||||||
|
public ResponseEntity<JsonElement> statsMaster() {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
// response.add("Proxy Groups Loaded", api.getGroupService().getProxyGroups().size());
|
||||||
|
// response.add("Server Groups Loaded", api.getGroupService().getServerGroups().size());
|
||||||
|
// response.add("Static Servers Loaded", api.getGroupService().getStaticServers().size());
|
||||||
|
//
|
||||||
|
// response.add("Wrappers Connected", api.getWrapperTracker().getTracked().size());
|
||||||
|
// response.add("Proxies Connected", api.getProxyTracker().getTracked().size());
|
||||||
|
// response.add("Servers Connected", api.getServerTracker().getTracked().size());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/stats/network")
|
||||||
|
public ResponseEntity<JsonElement> statsNetwork() {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
response.add("Registered Players", api.getMongoService().getProfiles().countDocuments());
|
||||||
|
// response.add("Player Peak (All Time)", api.getStatsTracker().getPlayerPeak());
|
||||||
|
// response.add("Player Peak (Daily)", api.getStatsTracker().getDailyPlayerPeak());
|
||||||
|
// response.add("Processed Logins (24h)", api.getStatsTracker().getProcessedLogins());
|
||||||
|
// response.add("New players registered (24h)", api.getStatsTracker().getRegistrations());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/stats/combined")
|
||||||
|
public ResponseEntity<JsonElement> statsCombined() {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
response.add("cache", statsCache().getBody());
|
||||||
|
response.add("api", statsApi().getBody());
|
||||||
|
response.add("master", statsMaster().getBody());
|
||||||
|
response.add("network", statsNetwork().getBody());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/oplist")
|
||||||
|
public ResponseEntity<JsonElement> oplist() {
|
||||||
|
JsonArray oplist = new JsonArray();
|
||||||
|
api.getMainConfig().getOpList().forEach(uuid -> oplist.add(uuid.toString()));
|
||||||
|
return new ResponseEntity<>(oplist, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/oplist/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> addToOpList(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (api.getMainConfig().getOpList().contains(uuid)) {
|
||||||
|
response.add("message", "Already on op list");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getMainConfig().getOpList().add(uuid);
|
||||||
|
api.saveMainConfig();
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/oplist/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> removeFromOplist(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (!api.getMainConfig().getOpList().contains(uuid)) {
|
||||||
|
response.add("message", "Not on op list");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getMainConfig().getOpList().remove(uuid);
|
||||||
|
api.saveMainConfig();
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
API/src/main/java/org/hcrival/api/banphrase/Banphrase.java
Normal file
56
API/src/main/java/org/hcrival/api/banphrase/Banphrase.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package org.hcrival.api.banphrase;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Banphrase {
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
private String name;
|
||||||
|
private String phrase;
|
||||||
|
private String operator;
|
||||||
|
private String muteMode;
|
||||||
|
private long duration;
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean caseSensitive;
|
||||||
|
|
||||||
|
public Banphrase(Document document) {
|
||||||
|
this.id = UUID.fromString(document.getString("id"));
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.phrase = document.getString("phrase");
|
||||||
|
this.operator = document.getString("operator");
|
||||||
|
this.muteMode = document.getString("muteMode");
|
||||||
|
this.duration = document.get("duration", Number.class).longValue();
|
||||||
|
this.enabled = document.getBoolean("enabled");
|
||||||
|
this.caseSensitive = document.getBoolean("caseSensitive");
|
||||||
|
|
||||||
|
if (!caseSensitive && !operator.equals("REGEX"))
|
||||||
|
this.phrase = phrase.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id.toString());
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("phrase", phrase);
|
||||||
|
document.append("operator", operator);
|
||||||
|
document.append("muteMode", muteMode);
|
||||||
|
document.append("duration", duration);
|
||||||
|
document.append("enabled", enabled);
|
||||||
|
document.append("caseSensitive", caseSensitive);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package org.hcrival.api.banphrase;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/banphrase")
|
||||||
|
public class BanphraseController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final BanphraseService banphraseService;
|
||||||
|
|
||||||
|
public BanphraseController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.banphraseService = api.getBanphraseService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> getBanphrases() {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
for (Banphrase banphrase : banphraseService.getBanphrases()) {
|
||||||
|
array.add(banphrase.toJson());
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createBanphrase(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
UUID id = body.has("id")
|
||||||
|
? UUID.fromString(body.get("id").getAsString())
|
||||||
|
: UUID.randomUUID();
|
||||||
|
|
||||||
|
if (banphraseService.getBanphrase(id).isPresent()) {
|
||||||
|
response.add("message", "Banphrase already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Banphrase banphrase = new Banphrase();
|
||||||
|
banphrase.setId(id);
|
||||||
|
banphrase.setName(body.get("name").getAsString());
|
||||||
|
banphrase.setPhrase(body.get("phrase").getAsString());
|
||||||
|
banphrase.setOperator(body.get("operator").getAsString());
|
||||||
|
banphrase.setMuteMode(body.get("muteMode").getAsString());
|
||||||
|
banphrase.setDuration(body.get("duration").getAsLong());
|
||||||
|
banphrase.setCaseSensitive(body.has("caseSensitive") && body.get("caseSensitive").getAsBoolean());
|
||||||
|
banphrase.setEnabled(!body.has("enabled") || body.get("enabled").getAsBoolean());
|
||||||
|
|
||||||
|
if (!banphrase.isCaseSensitive() && !banphrase.getOperator().equals("REGEX"))
|
||||||
|
banphrase.setPhrase(banphrase.getPhrase().toLowerCase());
|
||||||
|
|
||||||
|
banphraseService.saveBanphrase(banphrase);
|
||||||
|
return new ResponseEntity<>(banphrase.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> updateBanphrase(@RequestBody JsonObject body, @PathVariable(name = "id") UUID id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<Banphrase> banphraseOpt = banphraseService.getBanphrase(id);
|
||||||
|
if (!banphraseOpt.isPresent()) {
|
||||||
|
response.add("message", "Banphrase not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Banphrase banphrase = banphraseOpt.get();
|
||||||
|
|
||||||
|
if (body.has("name"))
|
||||||
|
banphrase.setName(body.get("name").getAsString());
|
||||||
|
|
||||||
|
if (body.has("caseSensitive"))
|
||||||
|
banphrase.setCaseSensitive(body.get("caseSensitive").getAsBoolean());
|
||||||
|
|
||||||
|
if (body.has("enabled"))
|
||||||
|
banphrase.setEnabled(body.get("enabled").getAsBoolean());
|
||||||
|
|
||||||
|
if (body.has("operator"))
|
||||||
|
banphrase.setOperator(body.get("operator").getAsString());
|
||||||
|
|
||||||
|
if (body.has("muteMode"))
|
||||||
|
banphrase.setMuteMode(body.get("muteMode").getAsString());
|
||||||
|
|
||||||
|
if (body.has("duration"))
|
||||||
|
banphrase.setDuration(body.get("duration").getAsLong());
|
||||||
|
|
||||||
|
if (body.has("phrase")) {
|
||||||
|
if (!banphrase.isCaseSensitive() && !banphrase.getOperator().equals("REGEX"))
|
||||||
|
banphrase.setPhrase(body.get("phrase").getAsString().toLowerCase());
|
||||||
|
else banphrase.setPhrase(body.get("phrase").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
banphraseService.saveBanphrase(banphrase);
|
||||||
|
return new ResponseEntity<>(banphrase.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> deleteBanphrase(@PathVariable(name = "id") UUID id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (!banphraseService.getBanphrase(id).isPresent()) {
|
||||||
|
response.add("message", "Banphrase not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Banphrase banphrase = banphraseService.deleteBanphrase(id);
|
||||||
|
return new ResponseEntity<>(banphrase.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.hcrival.api.banphrase;
|
||||||
|
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BanphraseService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Map<UUID, Banphrase> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loadBanphrases() {
|
||||||
|
cache.clear();
|
||||||
|
api.getMongoService().getBanphrases().find()
|
||||||
|
.forEach((Block<? super Document>) document -> {
|
||||||
|
Banphrase banphrase = new Banphrase(document);
|
||||||
|
cache.put(banphrase.getId(), banphrase);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Banphrase> getBanphrases() {
|
||||||
|
return new ArrayList<>(cache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Banphrase> getBanphrase(UUID id) {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveBanphrase(Banphrase banphrase) {
|
||||||
|
api.getMongoService().getBanphrases().replaceOne(
|
||||||
|
Filters.eq("id", banphrase.getId().toString()),
|
||||||
|
banphrase.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(banphrase.getId(), banphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Banphrase deleteBanphrase(UUID id) {
|
||||||
|
api.getMongoService().getBanphrases().deleteOne(Filters.eq("id", id.toString()));
|
||||||
|
return cache.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
API/src/main/java/org/hcrival/api/config/MainConfig.java
Normal file
21
API/src/main/java/org/hcrival/api/config/MainConfig.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package org.hcrival.api.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.util.configuration.StaticConfiguration;
|
||||||
|
import org.hcrival.api.util.configuration.defaults.MongoConfig;
|
||||||
|
import org.hcrival.api.util.configuration.defaults.RedisConfig;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class MainConfig implements StaticConfiguration {
|
||||||
|
|
||||||
|
private final List<UUID> opList = new ArrayList<>();
|
||||||
|
private RedisConfig redisConfig = new RedisConfig();
|
||||||
|
private MongoConfig mongoConfig = new MongoConfig();
|
||||||
|
|
||||||
|
}
|
345
API/src/main/java/org/hcrival/api/discord/DiscordController.java
Normal file
345
API/src/main/java/org/hcrival/api/discord/DiscordController.java
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
package org.hcrival.api.discord;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.profile.ProfileService;
|
||||||
|
import org.hcrival.api.profile.grant.Grant;
|
||||||
|
import org.hcrival.api.profile.grant.GrantService;
|
||||||
|
import org.hcrival.api.rank.RankService;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/discord")
|
||||||
|
public class DiscordController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final DiscordService discordService;
|
||||||
|
private final ProfileService profileService;
|
||||||
|
private final GrantService grantService;
|
||||||
|
private final RankService rankService;
|
||||||
|
|
||||||
|
public DiscordController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.discordService = api.getDiscordService();
|
||||||
|
this.profileService = api.getProfileService();
|
||||||
|
this.grantService = api.getGrantService();
|
||||||
|
this.rankService = api.getRankService();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(path = "/hasboosted/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> hasBoosted(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DiscordData> data = discordService.getByUuid(uuid);
|
||||||
|
|
||||||
|
boolean boosted = data.isPresent() && data.get().isBoosted();
|
||||||
|
response.add("boosted", boosted);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/issynced/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> isSynced(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DiscordData> data = discordService.getByUuid(uuid);
|
||||||
|
|
||||||
|
boolean synced = data.isPresent() && data.get().getMemberId() != null;
|
||||||
|
response.add("synced", synced);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{memberId}")
|
||||||
|
public ResponseEntity<JsonElement> getMember(@PathVariable(name = "memberId") String memberId) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<DiscordData> dataOpt = discordService.getByMemberId(memberId);
|
||||||
|
if (!dataOpt.isPresent()) {
|
||||||
|
response.add("message", "Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordData data = dataOpt.get();
|
||||||
|
JsonObject dataJson = data.toJson();
|
||||||
|
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(data.getUuid());
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataJson.addProperty("displayName",
|
||||||
|
profileOpt.get().getRealCurrentGrant().asRank().getColor() + profileOpt.get().getName());
|
||||||
|
dataJson.addProperty("name", profileOpt.get().getName());
|
||||||
|
|
||||||
|
|
||||||
|
return new ResponseEntity<>(dataJson, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/ingame")
|
||||||
|
public ResponseEntity<JsonElement> validateInGameSyncRequest(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
UUID uuid = UUID.fromString(body.get("uuid").getAsString());
|
||||||
|
DiscordData data = discordService.getByUuid(uuid).orElse(null);
|
||||||
|
|
||||||
|
if (data != null && data.getMemberId() != null) {
|
||||||
|
response.add("alreadySynced", true);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
response.add("code", data.getSyncCode());
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = body.get("code").getAsString();
|
||||||
|
if (!discordService.isCodeAvailable(code)) {
|
||||||
|
response.add("message", "Code already in use");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = new DiscordData();
|
||||||
|
data.setUuid(uuid);
|
||||||
|
data.setSyncCode(code);
|
||||||
|
discordService.saveData(data);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/discord")
|
||||||
|
public ResponseEntity<JsonElement> validateDiscordSyncRequest(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DiscordData> dataOpt = discordService.getByCode(body.get("code").getAsString());
|
||||||
|
|
||||||
|
if (!dataOpt.isPresent()) {
|
||||||
|
response.add("message", "Discord Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordData data = dataOpt.get();
|
||||||
|
if (data.getMemberId() != null) {
|
||||||
|
response.add("invalidCode", true);
|
||||||
|
response.add("message", "Code already used");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
String memberId = body.get("memberId").getAsString();
|
||||||
|
if (discordService.getByMemberId(memberId).isPresent()) {
|
||||||
|
response.add("message", "Member already synced");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setMemberId(memberId);
|
||||||
|
data.setBoosted(body.get("boosted").getAsBoolean());
|
||||||
|
discordService.saveData(data);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
public ResponseEntity<JsonElement> memberUpdate(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DiscordData> dataOpt = discordService.getByMemberId(body.get("memberId").getAsString());
|
||||||
|
|
||||||
|
if (!dataOpt.isPresent()) {
|
||||||
|
response.add("message", "Discord Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordData data = dataOpt.get();
|
||||||
|
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(data.getUuid());
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setBoosted(body.get("boosted").getAsBoolean());
|
||||||
|
|
||||||
|
Profile profile = profileOpt.get();
|
||||||
|
|
||||||
|
List<String> currentDiscordRanks = new ArrayList<>();
|
||||||
|
List<String> toRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
body.get("ranks").getAsJsonArray().forEach(element -> currentDiscordRanks.add(element.getAsString()));
|
||||||
|
currentDiscordRanks.removeIf(rank -> !rankService.getByDiscordId(rank).isPresent());
|
||||||
|
|
||||||
|
List<Grant> grants = grantService.getGrantsOf(profile.getUuid());
|
||||||
|
|
||||||
|
List<String> allDiscordRanks = grants.stream()
|
||||||
|
.filter(grant -> grant.asRank().getDiscordId() != null
|
||||||
|
&& grant.isActive()
|
||||||
|
&& !grant.isRemoved())
|
||||||
|
.map(grant -> grant.asRank().getDiscordId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<String> toAdd = grants.stream()
|
||||||
|
.filter(grant -> grant.asRank().getDiscordId() != null
|
||||||
|
&& !currentDiscordRanks.contains(grant.asRank().getDiscordId())
|
||||||
|
&& grant.isActive()
|
||||||
|
&& !grant.isRemoved())
|
||||||
|
.map(grant -> grant.asRank().getDiscordId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (String s : currentDiscordRanks) {
|
||||||
|
boolean hasRank = grants.stream()
|
||||||
|
.anyMatch(grant -> grant.asRank().getDiscordId() != null
|
||||||
|
&& grant.asRank().getDiscordId().equals(s)
|
||||||
|
&& grant.isActive()
|
||||||
|
&& !grant.isRemoved());
|
||||||
|
|
||||||
|
if (data.isRequestedRemoval() || (!hasRank && !allDiscordRanks.contains(s)))
|
||||||
|
toRemove.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.isRequestedRemoval())
|
||||||
|
toAdd.clear();
|
||||||
|
|
||||||
|
{
|
||||||
|
JsonArray toAddArray = new JsonArray();
|
||||||
|
toAdd.forEach(toAddArray::add);
|
||||||
|
response.add("toAdd", toAddArray);
|
||||||
|
|
||||||
|
JsonArray toRemoveArray = new JsonArray();
|
||||||
|
toRemove.forEach(toRemoveArray::add);
|
||||||
|
response.add("toRemove", toRemoveArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
allDiscordRanks.clear();
|
||||||
|
currentDiscordRanks.clear();
|
||||||
|
body.get("staffRanks").getAsJsonArray().forEach(element -> currentDiscordRanks.add(element.getAsString()));
|
||||||
|
toAdd.clear();
|
||||||
|
toRemove.clear();
|
||||||
|
|
||||||
|
allDiscordRanks.addAll(grants.stream()
|
||||||
|
.filter(grant -> grant.asRank().getDiscordId() != null
|
||||||
|
&& grant.isActive()
|
||||||
|
&& !grant.isRemoved())
|
||||||
|
.map(grant -> grant.asRank().getDiscordId())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
toAdd.addAll(grants.stream()
|
||||||
|
.filter(grant -> grant.asRank().getStaffDiscordId() != null
|
||||||
|
&& !currentDiscordRanks.contains(grant.asRank().getStaffDiscordId())
|
||||||
|
&& grant.isActive()
|
||||||
|
&& !grant.isRemoved())
|
||||||
|
.map(grant -> grant.asRank().getStaffDiscordId())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
for (String s : currentDiscordRanks) {
|
||||||
|
boolean hasRank = grants.stream()
|
||||||
|
.anyMatch(grant -> grant.asRank().getStaffDiscordId() != null
|
||||||
|
&& grant.asRank().getStaffDiscordId().equals(s)
|
||||||
|
&& grant.isActive()
|
||||||
|
&& !grant.isRemoved());
|
||||||
|
|
||||||
|
if (data.isRequestedRemoval() || (!hasRank && !allDiscordRanks.contains(s)))
|
||||||
|
toRemove.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.isRequestedRemoval())
|
||||||
|
toAdd.clear();
|
||||||
|
|
||||||
|
currentDiscordRanks.removeIf(toRemove::contains);
|
||||||
|
boolean staffAccess = (!toAdd.isEmpty() || !currentDiscordRanks.isEmpty()) && !data.isRequestedRemoval();
|
||||||
|
|
||||||
|
{
|
||||||
|
JsonArray toAddArray = new JsonArray();
|
||||||
|
toAdd.forEach(toAddArray::add);
|
||||||
|
response.add("staffToAdd", toAddArray);
|
||||||
|
|
||||||
|
JsonArray toRemoveArray = new JsonArray();
|
||||||
|
toRemove.forEach(toRemoveArray::add);
|
||||||
|
response.add("staffToRemove", toRemoveArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.add("requestedRemoval", data.isRequestedRemoval());
|
||||||
|
response.add("staffAccess", staffAccess);
|
||||||
|
response.add("nickname",
|
||||||
|
(profile.getRealCurrentGrant().asRank().getWeight() > 0
|
||||||
|
? "[" + profile.getRealCurrentGrant().asRank().getName() + "] " : "")
|
||||||
|
+ profile.getName());
|
||||||
|
|
||||||
|
discordService.saveData(data);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/membersToUpdate")
|
||||||
|
public ResponseEntity<JsonElement> getMembersToUpdate() {
|
||||||
|
JsonArray ids = new JsonArray();
|
||||||
|
discordService.getAllMemberIds().forEach(ids::add);
|
||||||
|
return new ResponseEntity<>(ids, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/requestRemoval/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> requestRemoval(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DiscordData> dataOpt = discordService.getByUuid(uuid);
|
||||||
|
|
||||||
|
if (!dataOpt.isPresent()) {
|
||||||
|
response.add("message", "Discord Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordData data = dataOpt.get();
|
||||||
|
if (data.isRequestedRemoval()) {
|
||||||
|
response.add("message", "Already requested removal");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setRequestedRemoval(true);
|
||||||
|
discordService.saveData(data);
|
||||||
|
return new ResponseEntity<>(data.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/verifystaff")
|
||||||
|
public ResponseEntity<JsonElement> verifyStaffAccess(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DiscordData> dataOpt = discordService.getByMemberId(body.get("memberId").getAsString());
|
||||||
|
|
||||||
|
if (!dataOpt.isPresent()) {
|
||||||
|
response.add("message", "Discord Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordData data = dataOpt.get();
|
||||||
|
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(data.getUuid());
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profile = profileOpt.get();
|
||||||
|
List<Grant> grants = grantService.getGrantsOf(profile.getUuid()).stream()
|
||||||
|
.filter(grant -> grant.isActive()
|
||||||
|
&& !grant.isRemoved()
|
||||||
|
&& grant.asRank().getStaffDiscordId() != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
response.add("access", !grants.isEmpty());
|
||||||
|
response.add("nickname",
|
||||||
|
(profile.getRealCurrentGrant().asRank().getWeight() > 0
|
||||||
|
? "[" + profile.getRealCurrentGrant().asRank().getName() + "] " : "")
|
||||||
|
+ profile.getName());
|
||||||
|
|
||||||
|
JsonArray ranks = new JsonArray();
|
||||||
|
grants.forEach(grant -> ranks.add(grant.asRank().getStaffDiscordId()));
|
||||||
|
response.add("ranks", ranks);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
API/src/main/java/org/hcrival/api/discord/DiscordData.java
Normal file
51
API/src/main/java/org/hcrival/api/discord/DiscordData.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package org.hcrival.api.discord;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DiscordData {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String memberId;
|
||||||
|
private String syncCode;
|
||||||
|
private boolean boosted;
|
||||||
|
private boolean requestedRemoval;
|
||||||
|
|
||||||
|
public DiscordData(Document document) {
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
if (document.containsKey("memberId"))
|
||||||
|
this.memberId = document.getString("memberId");
|
||||||
|
this.syncCode = document.getString("syncCode");
|
||||||
|
this.boosted = document.getBoolean("boosted");
|
||||||
|
if (document.containsKey("requestedRemoval"))
|
||||||
|
this.requestedRemoval = document.getBoolean("requestedRemoval");
|
||||||
|
else requestedRemoval = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
return new Document()
|
||||||
|
.append("uuid", uuid.toString())
|
||||||
|
.append("memberId", memberId)
|
||||||
|
.append("syncCode", syncCode)
|
||||||
|
.append("boosted", boosted)
|
||||||
|
.append("requestedRemoval", requestedRemoval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return new JsonBuilder()
|
||||||
|
.add("uuid", uuid.toString())
|
||||||
|
.add("memberId", memberId)
|
||||||
|
.add("syncCode", syncCode)
|
||||||
|
.add("boosted", boosted)
|
||||||
|
.add("requestedRemoval", requestedRemoval)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
110
API/src/main/java/org/hcrival/api/discord/DiscordService.java
Normal file
110
API/src/main/java/org/hcrival/api/discord/DiscordService.java
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package org.hcrival.api.discord;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DiscordService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, DiscordData> uuidCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, DiscordData>() {
|
||||||
|
@Override
|
||||||
|
public DiscordData load(UUID uuid) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getDiscordData()
|
||||||
|
.find(Filters.eq("uuid", uuid.toString())).first();
|
||||||
|
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new DiscordData(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private final LoadingCache<String, DiscordData> memberIdCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<String, DiscordData>() {
|
||||||
|
@Override
|
||||||
|
public DiscordData load(String memberId) throws DataNotFoundException {
|
||||||
|
|
||||||
|
Document document = api.getMongoService().getDiscordData()
|
||||||
|
.find(Filters.eq("memberId", memberId)).first();
|
||||||
|
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
return new DiscordData(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<DiscordData> getByUuid(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(uuidCache.get(uuid));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DiscordData> getByMemberId(String memberId) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(memberIdCache.get(memberId));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveData(DiscordData data) {
|
||||||
|
api.getMongoService().getDiscordData().replaceOne(
|
||||||
|
Filters.eq("uuid", data.getUuid().toString()),
|
||||||
|
data.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
uuidCache.put(data.getUuid(), data);
|
||||||
|
if (data.getMemberId() != null)
|
||||||
|
memberIdCache.put(data.getMemberId(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DiscordData> getByCode(String code) {
|
||||||
|
Document document = api.getMongoService().getDiscordData().find(Filters.eq("syncCode", code)).first();
|
||||||
|
if (document == null)
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
return getByUuid(UUID.fromString(document.getString("uuid")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCodeAvailable(String code) {
|
||||||
|
return api.getMongoService().getDiscordData().find(Filters.eq("syncCode", code)).first() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllMemberIds() {
|
||||||
|
List<String> memberIds = new ArrayList<>();
|
||||||
|
for (Document document : api.getMongoService().getDiscordData().find()) {
|
||||||
|
if (document.containsKey("memberId") && document.get("memberId") != null)
|
||||||
|
memberIds.add(document.getString("memberId"));
|
||||||
|
}
|
||||||
|
return memberIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package org.hcrival.api.disguise;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.xml.ws.Response;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/disguise")
|
||||||
|
public class DisguiseController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final DisguiseService disguiseService;
|
||||||
|
|
||||||
|
public DisguiseController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.disguiseService = api.getDisguiseService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getDisguiseData(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<DisguiseData> data = disguiseService.getData(uuid);
|
||||||
|
|
||||||
|
if (!data.isPresent()) {
|
||||||
|
response.add("message", "Disguise Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(data.get().toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createDisguiseData(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
DisguiseData data = JsonConfigurationService.gson.fromJson(body, DisguiseData.class);
|
||||||
|
if (disguiseService.getData(data.getUuid()).isPresent()) {
|
||||||
|
response.add("message", "Disguise Data already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
disguiseService.saveData(data);
|
||||||
|
return new ResponseEntity<>(data.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
public ResponseEntity<JsonElement> updateDisguiseData(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
DisguiseData data = JsonConfigurationService.gson.fromJson(body, DisguiseData.class);
|
||||||
|
if (!disguiseService.getData(data.getUuid()).isPresent()) {
|
||||||
|
response.add("message", "Disguise Data not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
disguiseService.saveData(data);
|
||||||
|
return new ResponseEntity<>(data.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{name}/available")
|
||||||
|
public ResponseEntity<JsonElement> isNameAvailable(@PathVariable(name = "name") String name) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
response.add("available", disguiseService.isNameAvailable(name));
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{name}/namelogs")
|
||||||
|
public ResponseEntity<JsonElement> getNameLogs(@PathVariable(name = "name") String name) {
|
||||||
|
JsonArray logs = new JsonArray();
|
||||||
|
disguiseService.getNameLogs(name).forEach(log -> logs.add(log.toJson()));
|
||||||
|
return new ResponseEntity<>(logs, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/presets/names")
|
||||||
|
public ResponseEntity<JsonElement> getNamePresets() {
|
||||||
|
JsonArray presets = new JsonArray();
|
||||||
|
disguiseService.getNamePresets().forEach(presets::add);
|
||||||
|
return new ResponseEntity<>(presets, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/presets/skins")
|
||||||
|
public ResponseEntity<JsonElement> getSkinPresets() {
|
||||||
|
JsonArray presets = new JsonArray();
|
||||||
|
disguiseService.getSkinPresets().forEach(preset -> presets.add(preset.toJson()));
|
||||||
|
return new ResponseEntity<>(presets, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
61
API/src/main/java/org/hcrival/api/disguise/DisguiseData.java
Normal file
61
API/src/main/java/org/hcrival/api/disguise/DisguiseData.java
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package org.hcrival.api.disguise;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DisguiseData {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String disguiseName;
|
||||||
|
private UUID disguiseRank;
|
||||||
|
private String texture;
|
||||||
|
private String signature;
|
||||||
|
private List<DisguiseLogEntry> logs;
|
||||||
|
|
||||||
|
public DisguiseData(Document document) {
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.disguiseName = document.getString("disguiseName");
|
||||||
|
|
||||||
|
if (document.containsKey("disguiseRank"))
|
||||||
|
this.disguiseRank = UUID.fromString(document.getString("disguiseRank"));
|
||||||
|
|
||||||
|
if (document.containsKey("texture"))
|
||||||
|
this.texture = document.getString("texture");
|
||||||
|
|
||||||
|
if (document.containsKey("signature"))
|
||||||
|
this.signature = document.getString("signature");
|
||||||
|
|
||||||
|
this.logs = document.getList("logs", Document.class).stream()
|
||||||
|
.map(DisguiseLogEntry::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("disguiseName", disguiseName);
|
||||||
|
document.append("disguiseNameLowerCase", disguiseName.toLowerCase());
|
||||||
|
document.append("disguiseRank", disguiseRank.toString());
|
||||||
|
document.append("texture", texture);
|
||||||
|
document.append("signature", signature);
|
||||||
|
document.append("logs", logs.stream()
|
||||||
|
.map(DisguiseLogEntry::toBson)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package org.hcrival.api.disguise;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DisguiseLogEntry {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String name;
|
||||||
|
private String rank;
|
||||||
|
private long timeStamp;
|
||||||
|
private long removedAt;
|
||||||
|
|
||||||
|
public DisguiseLogEntry(Document document) {
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.rank = document.getString("rank");
|
||||||
|
this.timeStamp = document.get("timeStamp", Number.class).longValue();
|
||||||
|
this.removedAt = document.get("removedAt", Number.class).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("nameLowerCase", name.toLowerCase());
|
||||||
|
document.append("rank", rank);
|
||||||
|
document.append("timeStamp", timeStamp);
|
||||||
|
document.append("removedAt", removedAt);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package org.hcrival.api.disguise;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.disguise.config.DisguiseConfig;
|
||||||
|
import org.hcrival.api.disguise.config.DisguiseSkinPreset;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DisguiseService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private DisguiseConfig disguiseConfig;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, DisguiseData> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, DisguiseData>() {
|
||||||
|
@Override
|
||||||
|
public DisguiseData load(UUID uuid) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getDisguiseData()
|
||||||
|
.find(Filters.eq("uuid", uuid.toString())).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new DisguiseData(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public void loadPresets() {
|
||||||
|
disguiseConfig = api.getConfigurationService().loadConfiguration(DisguiseConfig.class,
|
||||||
|
new File("./disguisePresets.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DisguiseData> getData(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(uuid));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveData(DisguiseData data) {
|
||||||
|
api.getMongoService().getDisguiseData().replaceOne(
|
||||||
|
Filters.eq("uuid", data.getUuid().toString()),
|
||||||
|
data.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(data.getUuid(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNameAvailable(String name) {
|
||||||
|
return api.getMongoService().getDisguiseData().find(
|
||||||
|
Filters.eq("disguiseNameLowerCase", name.toLowerCase())).first() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DisguiseLogEntry> getNameLogs(String name) {
|
||||||
|
List<DisguiseLogEntry> toReturn = new ArrayList<>();
|
||||||
|
api.getMongoService().getDisguiseData().find(Filters.elemMatch("logs",
|
||||||
|
Filters.eq("nameLowerCase", name.toLowerCase())))
|
||||||
|
.forEach((Block<? super Document>) document ->
|
||||||
|
toReturn.addAll(document.getList("logs", Document.class).stream()
|
||||||
|
.map(DisguiseLogEntry::new)
|
||||||
|
.filter(log -> log.getName().equalsIgnoreCase(name))
|
||||||
|
.collect(Collectors.toList())));
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getNamePresets() {
|
||||||
|
return disguiseConfig.getNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DisguiseSkinPreset> getSkinPresets() {
|
||||||
|
return disguiseConfig.getSkins();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.hcrival.api.disguise.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.util.configuration.StaticConfiguration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DisguiseConfig implements StaticConfiguration {
|
||||||
|
|
||||||
|
private List<String> names = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<DisguiseSkinPreset> skins = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.hcrival.api.disguise.config;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DisguiseSkinPreset {
|
||||||
|
|
||||||
|
private String name = "N/A";
|
||||||
|
private boolean hidden = false;
|
||||||
|
private String texture;
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
API/src/main/java/org/hcrival/api/forum/ForumService.java
Normal file
40
API/src/main/java/org/hcrival/api/forum/ForumService.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package org.hcrival.api.forum;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.account.AccountService;
|
||||||
|
import org.hcrival.api.forum.category.CategoryService;
|
||||||
|
import org.hcrival.api.forum.forum.ForumModelService;
|
||||||
|
import org.hcrival.api.forum.thread.ThreadService;
|
||||||
|
import org.hcrival.api.forum.ticket.TicketService;
|
||||||
|
import org.hcrival.api.forum.trophy.TrophyService;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class ForumService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
private final AccountService accountService;
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
private final ForumModelService forumModelService;
|
||||||
|
private final ThreadService threadService;
|
||||||
|
private final TicketService ticketService;
|
||||||
|
private final TrophyService trophyService;
|
||||||
|
|
||||||
|
public ForumService(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.accountService = new AccountService(api);
|
||||||
|
|
||||||
|
this.categoryService = new CategoryService(api);
|
||||||
|
categoryService.loadCategories();
|
||||||
|
|
||||||
|
this.forumModelService = new ForumModelService(api);
|
||||||
|
forumModelService.loadForums();
|
||||||
|
|
||||||
|
this.threadService = new ThreadService(api);
|
||||||
|
this.ticketService = new TicketService(api);
|
||||||
|
|
||||||
|
this.trophyService = new TrophyService(api);
|
||||||
|
trophyService.loadTrophies();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
package org.hcrival.api.forum.account;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.thread.ForumThread;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.UUIDCache;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/forum/account")
|
||||||
|
public class AccountController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@PostMapping(path = "/sendRegistration/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> sendRegistration(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<Profile> profileOpt = api.getProfileService().getProfile(uuid);
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profile = profileOpt.get();
|
||||||
|
|
||||||
|
Optional<ForumAccount> accountOpt = api.getForumService().getAccountService().getAccount(uuid);
|
||||||
|
if (accountOpt.isPresent()) {
|
||||||
|
response.add("message", "Already registered");
|
||||||
|
response.add("registered", true);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
accountOpt = api.getForumService().getAccountService().getByEmail(body.get("email").getAsString());
|
||||||
|
if (accountOpt.isPresent()) {
|
||||||
|
response.add("message", "Email in use");
|
||||||
|
response.add("emailInUse", true);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.getForumService().getAccountService().getByToken(body.get("token").getAsString()).isPresent()) {
|
||||||
|
response.add("message", "Invalid token");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumAccount account = new ForumAccount();
|
||||||
|
account.setUuid(profile.getUuid());
|
||||||
|
account.setEmail(body.get("email").getAsString().toLowerCase());
|
||||||
|
account.setToken(body.get("token").getAsString());
|
||||||
|
api.getForumService().getAccountService().saveAccount(account);
|
||||||
|
|
||||||
|
String format = String.format(
|
||||||
|
"http://70.34.207.5:8081/register?token=%s&email=%s&username=%s",
|
||||||
|
body.get("token").getAsString(),
|
||||||
|
body.get("email").getAsString(),
|
||||||
|
profile.getName()
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.println(format);
|
||||||
|
|
||||||
|
// Optional<Player> playerOpt = api.getPlayerService().getPlayer(profile.getUuid());
|
||||||
|
// playerOpt.ifPresent(player -> {
|
||||||
|
// player.sendMessage(format);
|
||||||
|
// });
|
||||||
|
|
||||||
|
response.add("message", "Confirmation email sent");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CREATED);
|
||||||
|
|
||||||
|
/*Properties properties = new Properties();
|
||||||
|
properties.put("mail.smtp.host", "mail.privateemail.com");
|
||||||
|
properties.put("mail.smtp.auth", "true");
|
||||||
|
properties.put("mail.smtp.port", "587");
|
||||||
|
properties.put("mail.smtp.starttls.enable", "true");
|
||||||
|
properties.put("mail.smtp.ssl.protocols", "TLSv1.2");
|
||||||
|
|
||||||
|
Session session = Session.getDefaultInstance(properties, new Authenticator() {
|
||||||
|
@Override
|
||||||
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
|
return new PasswordAuthentication("noreply@arson.gg", "BCj$z!K@kn8soft?");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
MimeMessage message = new MimeMessage(session);
|
||||||
|
message.setFrom(new InternetAddress("noreply@arson.gg", "Brave.rip Network"));
|
||||||
|
message.addRecipient(Message.RecipientType.TO, new InternetAddress(body.get("email").getAsString()));
|
||||||
|
message.setSubject("Brave.rip - Complete Your Registration.");
|
||||||
|
message.setText(
|
||||||
|
"Hey, " + profile.getName() + "!\n\n" +
|
||||||
|
"Thanks for registering for an account on the Brave.rip Network. To complete your " +
|
||||||
|
"account registration, please click the following link:\n\n" +
|
||||||
|
String.format(
|
||||||
|
"http://brave.rip/register?token=%s&email=%s&username=%s",
|
||||||
|
body.get("token").getAsString(),
|
||||||
|
body.get("email").getAsString(),
|
||||||
|
profile.getName()
|
||||||
|
) + "\n\n" +
|
||||||
|
"Thanks,\n" +
|
||||||
|
"The team at Brave.rip"
|
||||||
|
);
|
||||||
|
|
||||||
|
Transport.send(message);
|
||||||
|
|
||||||
|
} catch (MessagingException | UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
response.add("message", e.getClass().getSimpleName() + " when sending email");
|
||||||
|
return new Response(Status.INTERNAL_ERROR, response.build());
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/threads/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getThreads(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
List<ForumThread> profileThreads = api.getForumService().getThreadService()
|
||||||
|
.getProfileThreads(uuid, 0);
|
||||||
|
|
||||||
|
for (ForumThread profileThread : profileThreads)
|
||||||
|
array.add(profileThread.toJson());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/register")
|
||||||
|
public ResponseEntity<JsonElement> register(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String token = body.get("token").getAsString();
|
||||||
|
Optional<ForumAccount> accountOpt = api.getForumService().getAccountService().getByToken(token);
|
||||||
|
if (!accountOpt.isPresent()) {
|
||||||
|
response.add("message", "Token not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumAccount account = accountOpt.get();
|
||||||
|
|
||||||
|
if (account.getPassword() != null) {
|
||||||
|
response.add("message", "Token expired");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
account.setPassword(body.get("password").getAsString());
|
||||||
|
api.getForumService().getAccountService().saveAccount(account);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/login/{username}")
|
||||||
|
public ResponseEntity<JsonElement> login(@PathVariable(name = "username") String username) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumAccount> accountOpt;
|
||||||
|
if (username.contains("@"))
|
||||||
|
accountOpt = api.getForumService().getAccountService().getByEmail(username);
|
||||||
|
else {
|
||||||
|
UUID uuid = UUIDCache.getUuid(username);
|
||||||
|
if (uuid == null)
|
||||||
|
accountOpt = Optional.empty();
|
||||||
|
else accountOpt = api.getForumService().getAccountService().getAccount(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accountOpt.isPresent()) {
|
||||||
|
response.add("message", "Account not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(accountOpt.get().toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/setting/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> updateSetting(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumAccount> accountOpt = api.getForumService().getAccountService().getAccount(uuid);
|
||||||
|
if (!accountOpt.isPresent()) {
|
||||||
|
response.add("message", "Account not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumAccount account = accountOpt.get();
|
||||||
|
body.keySet().forEach(key -> account.getSettings().put(key, body.get(key).getAsString()));
|
||||||
|
api.getForumService().getAccountService().saveAccount(account);
|
||||||
|
return new ResponseEntity<>(account.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/password/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> updatePassword(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumAccount> accountOpt = api.getForumService().getAccountService().getAccount(uuid);
|
||||||
|
if (!accountOpt.isPresent()) {
|
||||||
|
response.add("message", "Account not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumAccount account = accountOpt.get();
|
||||||
|
if (!account.getPassword().equals(body.get("currentPassword").getAsString())) {
|
||||||
|
response.add("message", "Invalid password");
|
||||||
|
response.add("invalidPassword", true);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
account.setPassword(body.get("password").getAsString());
|
||||||
|
api.getForumService().getAccountService().saveAccount(account);
|
||||||
|
return new ResponseEntity<>(account.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package org.hcrival.api.forum.account;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AccountService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, ForumAccount> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, ForumAccount>() {
|
||||||
|
@Override
|
||||||
|
public ForumAccount load(UUID uuid) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getForumAccounts()
|
||||||
|
.find(Filters.eq("uuid", uuid.toString())).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new ForumAccount(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<ForumAccount> getByToken(String token) {
|
||||||
|
Document document = api.getMongoService().getForumAccounts().find(Filters.eq("token", token)).first();
|
||||||
|
if (document == null)
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
return Optional.of(new ForumAccount(document));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumAccount> getByEmail(String email) {
|
||||||
|
Document document = api.getMongoService().getForumAccounts().find(Filters.eq(
|
||||||
|
"email",
|
||||||
|
email.toLowerCase()
|
||||||
|
)).first();
|
||||||
|
|
||||||
|
if (document == null)
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
return Optional.of(new ForumAccount(document));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumAccount> getAccount(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(uuid));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAccount(ForumAccount account) {
|
||||||
|
api.getMongoService().getForumAccounts().replaceOne(
|
||||||
|
Filters.eq("uuid", account.getUuid().toString()),
|
||||||
|
account.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(account.getUuid(), account);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.hcrival.api.forum.account;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ForumAccount {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String email;
|
||||||
|
private String password;
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
private Map<String, String> settings = new HashMap<>();
|
||||||
|
|
||||||
|
public ForumAccount(Document document) {
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.email = document.getString("email");
|
||||||
|
this.password = document.getString("password");
|
||||||
|
this.token = document.getString("token");
|
||||||
|
|
||||||
|
Document settingsDocument = document.containsKey("settings")
|
||||||
|
? document.get("settings", Document.class)
|
||||||
|
: new Document();
|
||||||
|
|
||||||
|
settingsDocument.keySet().forEach(key -> settings.put(key, settingsDocument.getString(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("email", email);
|
||||||
|
document.append("password", password);
|
||||||
|
document.append("token", token);
|
||||||
|
|
||||||
|
Document settingsDocument = new Document();
|
||||||
|
settings.forEach(settingsDocument::append);
|
||||||
|
document.append("settings", settingsDocument);
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package org.hcrival.api.forum.category;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/forum/category")
|
||||||
|
public class CategoryController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
public CategoryController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.categoryService = this.api.getForumService().getCategoryService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> getCategories() {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
api.getForumService().getCategoryService().getCategories().forEach(category -> array.add(category.toJson()));
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createCategory(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String id = body.get("id").getAsString();
|
||||||
|
String name = body.get("name").getAsString();
|
||||||
|
|
||||||
|
Optional<ForumCategory> categoryOpt = categoryService.getByIdOrName(id);
|
||||||
|
if (categoryOpt.isPresent()) {
|
||||||
|
response.add("message", "Category already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryOpt = categoryService.getByIdOrName(name);
|
||||||
|
if (categoryOpt.isPresent()) {
|
||||||
|
response.add("message", "Category already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumCategory category = new ForumCategory();
|
||||||
|
category.setId(id);
|
||||||
|
category.setName(name);
|
||||||
|
category.setWeight(body.get("weight").getAsInt());
|
||||||
|
|
||||||
|
categoryService.saveCategory(category);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(category.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> updateCategory(@RequestBody JsonObject body, @PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumCategory> categoryOpt = categoryService.getById(id);
|
||||||
|
if (!categoryOpt.isPresent()) {
|
||||||
|
response.add("message", "Category not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumCategory category = categoryOpt.get();
|
||||||
|
if (body.has("name"))
|
||||||
|
category.setName(body.get("name").getAsString());
|
||||||
|
|
||||||
|
if (body.has("weight"))
|
||||||
|
category.setWeight(body.get("weight").getAsInt());
|
||||||
|
|
||||||
|
categoryService.saveCategory(category);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(category.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> deleteCategory(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumCategory> categoryOpt = categoryService.getById(id);
|
||||||
|
if (!categoryOpt.isPresent()) {
|
||||||
|
response.add("message", "Category not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumCategory category = categoryOpt.get();
|
||||||
|
categoryService.deleteCategory(category);
|
||||||
|
return new ResponseEntity<>(category.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package org.hcrival.api.forum.category;
|
||||||
|
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CategoryService {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Map<String, ForumCategory> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loadCategories() {
|
||||||
|
master.getMongoService().getForumCategories().find().forEach((Consumer<? super Document>) document -> {
|
||||||
|
ForumCategory category = new ForumCategory(document);
|
||||||
|
cache.put(category.getId(), category);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumCategory> getById(String id) {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumCategory> getByName(String name) {
|
||||||
|
for (ForumCategory category : cache.values()) {
|
||||||
|
if (category.getName().equalsIgnoreCase(name))
|
||||||
|
return Optional.of(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumCategory> getByIdOrName(String key) {
|
||||||
|
Optional<ForumCategory> optional = getById(key);
|
||||||
|
if (optional.isPresent())
|
||||||
|
return optional;
|
||||||
|
|
||||||
|
return getByName(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumCategory> getCategories() {
|
||||||
|
return new ArrayList<>(cache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveCategory(ForumCategory category) {
|
||||||
|
master.getMongoService().getForumCategories().replaceOne(
|
||||||
|
Filters.eq("id", category.getId()),
|
||||||
|
category.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(category.getId(), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteCategory(ForumCategory category) {
|
||||||
|
master.getMongoService().getForumCategories().deleteOne(Filters.eq("id", category.getId()));
|
||||||
|
cache.remove(category.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package org.hcrival.api.forum.category;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.forum.ForumModel;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ForumCategory {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private int weight;
|
||||||
|
|
||||||
|
public ForumCategory(Document document) {
|
||||||
|
this.id = document.getString("id");
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.weight = document.getInteger("weight");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id);
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("weight", weight);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
for (ForumModel forum : InvictusAPI.getInstance().getForumService().getForumModelService().getByCategory(id))
|
||||||
|
array.add(forum.toJson());
|
||||||
|
object.add("forums", array);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package org.hcrival.api.forum.forum;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.category.ForumCategory;
|
||||||
|
import org.hcrival.api.forum.thread.ForumThread;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ForumModel {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private int weight;
|
||||||
|
private boolean locked;
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
public ForumModel(Document document) {
|
||||||
|
this.id = document.getString("id");
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.description = document.getString("description");
|
||||||
|
this.weight = document.getInteger("weight");
|
||||||
|
this.locked = document.getBoolean("locked");
|
||||||
|
this.category = document.getString("category");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id);
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("description", description);
|
||||||
|
document.append("weight", weight);
|
||||||
|
document.append("locked", locked);
|
||||||
|
document.append("category", category);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
|
||||||
|
if (getParent() != null) {
|
||||||
|
object.addProperty("categoryName", getParent().getName());
|
||||||
|
object.addProperty("categoryWeight", getParent().getWeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumThread thread = InvictusAPI.getInstance().getForumService()
|
||||||
|
.getForumModelService().getLastThread(this).orElse(null);
|
||||||
|
|
||||||
|
if (thread != null)
|
||||||
|
object.add("lastThread", thread.toJson());
|
||||||
|
|
||||||
|
long threadAmount = InvictusAPI.getInstance().getForumService().getForumModelService().threadSize(this);
|
||||||
|
object.addProperty("threadAmount", threadAmount);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForumCategory getParent() {
|
||||||
|
return InvictusAPI.getInstance().getForumService().getCategoryService().getById(category).orElse(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package org.hcrival.api.forum.forum;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.thread.ForumThread;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/forum/forum")
|
||||||
|
public class ForumModelController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final ForumModelService forumService;
|
||||||
|
|
||||||
|
public ForumModelController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.forumService = api.getForumService().getForumModelService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createForum(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String id = body.get("id").getAsString();
|
||||||
|
Optional<ForumModel> forumOpt = forumService.getById(id);
|
||||||
|
if (forumOpt.isPresent()) {
|
||||||
|
response.add("message", "Forum already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumModel forum = new ForumModel();
|
||||||
|
forum.setId(id);
|
||||||
|
forum.setName(body.get("name").getAsString());
|
||||||
|
forum.setDescription(body.get("description").getAsString());
|
||||||
|
forum.setWeight(body.get("weight").getAsInt());
|
||||||
|
forum.setLocked(body.get("locked").getAsBoolean());
|
||||||
|
forum.setCategory(body.get("categoryId").getAsString());
|
||||||
|
|
||||||
|
forumService.saveForum(forum);
|
||||||
|
return new ResponseEntity<>(forum.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> editForum(@RequestBody JsonObject body, @PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumModel> forumOpt = forumService.getById(id);
|
||||||
|
if (!forumOpt.isPresent()) {
|
||||||
|
response.add("message", "Forum not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumModel forum = forumOpt.get();
|
||||||
|
|
||||||
|
if (body.has("name"))
|
||||||
|
forum.setName(body.get("name").getAsString());
|
||||||
|
|
||||||
|
if (body.has("description"))
|
||||||
|
forum.setDescription(body.get("description").getAsString());
|
||||||
|
|
||||||
|
if (body.has("weight"))
|
||||||
|
forum.setWeight(body.get("weight").getAsInt());
|
||||||
|
|
||||||
|
if (body.has("locked"))
|
||||||
|
forum.setLocked(body.get("locked").getAsBoolean());
|
||||||
|
|
||||||
|
forumService.saveForum(forum);
|
||||||
|
return new ResponseEntity<>(forum.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> deleteForum(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumModel> forumOpt = forumService.getById(id);
|
||||||
|
if (!forumOpt.isPresent()) {
|
||||||
|
response.add("message", "Forum not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumModel forum = forumOpt.get();
|
||||||
|
forumService.deleteForum(forum);
|
||||||
|
return new ResponseEntity<>(forum.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getForum(@PathVariable(name = "id") String id,
|
||||||
|
@RequestParam(name = "page", defaultValue = "-1") int page) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumModel> forumOpt = forumService.getByIdOrName(id.replace("-", " "));
|
||||||
|
if (!forumOpt.isPresent()) {
|
||||||
|
response.add("message", "Forum not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumModel forum = forumOpt.get();
|
||||||
|
JsonObject object = forum.toJson();
|
||||||
|
|
||||||
|
if (page > 0) {
|
||||||
|
List<ForumThread> threads = api.getForumService().getThreadService().getForumThreads(forum.getId(), page);
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
threads.forEach(thread -> array.add(thread.toJson()));
|
||||||
|
object.add("threads", array);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(object, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package org.hcrival.api.forum.forum;
|
||||||
|
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import com.mongodb.client.model.Sorts;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.thread.ForumThread;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ForumModelService {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Map<String, ForumModel> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loadForums() {
|
||||||
|
master.getMongoService().getForumForums().find().forEach((Block<? super Document>) document -> {
|
||||||
|
ForumModel forum = new ForumModel(document);
|
||||||
|
cache.put(forum.getId(), forum);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumModel> getById(String id) {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumModel> getByName(String name) {
|
||||||
|
for (ForumModel forum : cache.values()) {
|
||||||
|
if (forum.getName().equalsIgnoreCase(name))
|
||||||
|
return Optional.of(forum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumModel> getByIdOrName(String key) {
|
||||||
|
Optional<ForumModel> optional = getById(key);
|
||||||
|
if (optional.isPresent())
|
||||||
|
return optional;
|
||||||
|
|
||||||
|
return getByName(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumModel> getForums() {
|
||||||
|
return new ArrayList<>(cache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveForum(ForumModel forum) {
|
||||||
|
master.getMongoService().getForumForums().replaceOne(
|
||||||
|
Filters.eq("id", forum.getId()),
|
||||||
|
forum.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(forum.getId(), forum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteForum(ForumModel forum) {
|
||||||
|
master.getMongoService().getForumForums().deleteOne(Filters.eq("id", forum.getId()));
|
||||||
|
cache.remove(forum.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumModel> getByCategory(String categoryId) {
|
||||||
|
List<ForumModel> forums = new ArrayList<>();
|
||||||
|
for (ForumModel forum : getForums()) {
|
||||||
|
if (forum.getCategory().equalsIgnoreCase(categoryId))
|
||||||
|
forums.add(forum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return forums;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ForumThread> getLastThread(ForumModel model) {
|
||||||
|
Document document = master.getMongoService().getForumThreads().find(Filters.and(
|
||||||
|
Filters.eq("parentThreadId", null),
|
||||||
|
Filters.eq("forum", model.getId())
|
||||||
|
)).sort(Sorts.ascending("createdAt")).first();
|
||||||
|
|
||||||
|
if (document == null)
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
return Optional.of(new ForumThread(document));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long threadSize(ForumModel model) {
|
||||||
|
return master.getMongoService().getForumThreads().countDocuments(Filters.and(
|
||||||
|
Filters.eq("parentThreadId", null),
|
||||||
|
Filters.eq("forum", model.getId())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package org.hcrival.api.forum.search;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/search")
|
||||||
|
public class SearchController {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> search(@RequestParam(name = "query") String query,
|
||||||
|
@RequestParam(name = "limit", defaultValue = "6") int limit) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("^" + Pattern.quote(query), Pattern.CASE_INSENSITIVE);
|
||||||
|
master.getMongoService().getProfiles().find(Filters.regex("name", pattern))
|
||||||
|
.limit(limit)
|
||||||
|
.forEach((Block<? super Document>) document -> {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
object.addProperty("name", document.getString("name"));
|
||||||
|
object.addProperty("uuid", document.getString("uuid"));
|
||||||
|
array.add(object);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
123
API/src/main/java/org/hcrival/api/forum/thread/ForumThread.java
Normal file
123
API/src/main/java/org/hcrival/api/forum/thread/ForumThread.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package org.hcrival.api.forum.thread;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.forum.ForumModel;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ForumThread {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
private String forum;
|
||||||
|
private UUID author;
|
||||||
|
private long createdAt;
|
||||||
|
|
||||||
|
private UUID lastEditedBy;
|
||||||
|
private long lastEditedAt;
|
||||||
|
|
||||||
|
private long lastReplyAt;
|
||||||
|
|
||||||
|
private boolean pinned;
|
||||||
|
private boolean locked;
|
||||||
|
|
||||||
|
private String parentThreadId;
|
||||||
|
private transient List<ForumThread> replies = new ArrayList<>();
|
||||||
|
|
||||||
|
public ForumThread(Document document) {
|
||||||
|
this.id = document.getString("id");
|
||||||
|
this.title = document.getString("title");
|
||||||
|
this.body = document.getString("body");
|
||||||
|
this.forum = document.getString("forum");
|
||||||
|
this.author = UUID.fromString(document.getString("author"));
|
||||||
|
this.createdAt = document.getLong("createdAt");
|
||||||
|
this.lastEditedBy = document.getString("lastEditedBy") != null
|
||||||
|
? UUID.fromString(document.getString("lastEditedBy")) : null;
|
||||||
|
this.lastEditedAt = document.getLong("lastEditedAt");
|
||||||
|
this.lastReplyAt = document.getLong("lastReplyAt");
|
||||||
|
this.pinned = document.getBoolean("pinned");
|
||||||
|
this.locked = document.containsKey("locked") ? document.getBoolean("locked") : false;
|
||||||
|
this.parentThreadId = document.getString("parentThreadId");
|
||||||
|
|
||||||
|
document.getList("replies", Document.class)
|
||||||
|
.forEach(replyDocument -> replies.add(new ForumThread(replyDocument)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id);
|
||||||
|
document.append("title", title);
|
||||||
|
document.append("body", body);
|
||||||
|
document.append("forum", forum);
|
||||||
|
document.append("author", author.toString());
|
||||||
|
document.append("createdAt", createdAt);
|
||||||
|
document.append("lastEditedBy", lastEditedBy == null ? null : lastEditedBy.toString());
|
||||||
|
document.append("lastEditedAt", lastEditedAt);
|
||||||
|
document.append("lastReplyAt", lastReplyAt);
|
||||||
|
document.append("pinned", pinned);
|
||||||
|
document.append("locked", locked);
|
||||||
|
document.append("parentThreadId", parentThreadId);
|
||||||
|
|
||||||
|
List<Document> documents = new ArrayList<>();
|
||||||
|
replies.forEach(reply -> documents.add(reply.toBson()));
|
||||||
|
document.append("replies", documents);
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
|
||||||
|
Profile profile = InvictusAPI.getInstance().getProfileService().getProfile(author).orElse(null);
|
||||||
|
if (profile != null) {
|
||||||
|
object.addProperty("authorName", profile.getName());
|
||||||
|
object.addProperty("authorWebColor", profile.getRealCurrentGrant().asRank().getWebColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumModel forum = getParent();
|
||||||
|
if (forum != null)
|
||||||
|
object.addProperty("forumName", forum.getName());
|
||||||
|
|
||||||
|
if (lastEditedBy != null) {
|
||||||
|
profile = InvictusAPI.getInstance().getProfileService().getProfile(lastEditedBy).orElse(null);
|
||||||
|
|
||||||
|
if (profile != null) {
|
||||||
|
object.addProperty("lastEditedByName", profile.getName());
|
||||||
|
object.addProperty("lastEditedByWebColor", profile.getRealCurrentGrant().asRank().getWebColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
replies.forEach(reply -> array.add(reply.toJson()));
|
||||||
|
object.add("replies", array);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForumModel getParent() {
|
||||||
|
return forum != null
|
||||||
|
? InvictusAPI.getInstance().getForumService().getForumModelService().getById(forum).orElse(null)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForumThread getParentThread() {
|
||||||
|
return parentThreadId != null
|
||||||
|
? InvictusAPI.getInstance().getForumService().getThreadService().getThread(parentThreadId).orElse(null)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,208 @@
|
|||||||
|
package org.hcrival.api.forum.thread;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.apache.http.protocol.HTTP;
|
||||||
|
import org.apache.http.protocol.ResponseDate;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.forum.forum.ForumModel;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/forum/thread")
|
||||||
|
public class ThreadController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final ThreadService threadService;
|
||||||
|
|
||||||
|
public ThreadController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.threadService = api.getForumService().getThreadService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createThread(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String id = body.get("id").getAsString();
|
||||||
|
Optional<ForumThread> threadOpt = threadService.getThread(id);
|
||||||
|
if (threadOpt.isPresent()) {
|
||||||
|
response.add("message", "Thread already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumThread thread = new ForumThread();
|
||||||
|
thread.setId(id);
|
||||||
|
thread.setTitle(body.get("title").getAsString());
|
||||||
|
thread.setBody(body.get("body").getAsString());
|
||||||
|
thread.setForum(body.get("forumId").getAsString());
|
||||||
|
thread.setAuthor(UUID.fromString(body.get("author").getAsString()));
|
||||||
|
thread.setCreatedAt(System.currentTimeMillis());
|
||||||
|
thread.setLastEditedBy(null);
|
||||||
|
thread.setLastEditedAt(-1L);
|
||||||
|
thread.setLastReplyAt(-1L);
|
||||||
|
thread.setPinned(false);
|
||||||
|
thread.setLocked(false);
|
||||||
|
|
||||||
|
threadService.saveThread(thread);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(thread.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> editThread(@RequestBody JsonObject body, @PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumThread> threadOpt = threadService.getThread(id);
|
||||||
|
if (!threadOpt.isPresent()) {
|
||||||
|
response.add("message", "Thread not found.");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumThread thread = threadOpt.get();
|
||||||
|
|
||||||
|
if (body.has("title"))
|
||||||
|
thread.setTitle(body.get("title").getAsString());
|
||||||
|
|
||||||
|
if (body.has("body"))
|
||||||
|
thread.setBody(body.get("body").getAsString());
|
||||||
|
|
||||||
|
if (body.has("pinned"))
|
||||||
|
thread.setPinned(body.get("pinned").getAsBoolean());
|
||||||
|
|
||||||
|
if (body.has("locked"))
|
||||||
|
thread.setLocked(body.get("locked").getAsBoolean());
|
||||||
|
|
||||||
|
if (body.has("lastEditedBy"))
|
||||||
|
thread.setLastEditedBy(UUID.fromString(body.get("lastEditedBy").getAsString()));
|
||||||
|
|
||||||
|
if (body.has("lastEditedAt"))
|
||||||
|
thread.setLastEditedAt(body.get("lastEditedAt").getAsLong());
|
||||||
|
|
||||||
|
threadService.saveThread(thread);
|
||||||
|
return new ResponseEntity<>(thread.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> deleteThread(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumThread> threadOpt = threadService.getThread(id);
|
||||||
|
if (!threadOpt.isPresent()) {
|
||||||
|
response.add("message", "Thread not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumThread thread = threadOpt.get();
|
||||||
|
threadService.deleteThread(thread);
|
||||||
|
return new ResponseEntity<>(thread.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{parentId}/{id}")
|
||||||
|
public ResponseEntity<JsonElement> deleteReply(@PathVariable(name = "parentId") String parentId,
|
||||||
|
@PathVariable(name = "id") String replyId) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumThread> parentThreadOpt = threadService.getThread(parentId);
|
||||||
|
Optional<ForumThread> replyThreadOpt = threadService.getThread(replyId);
|
||||||
|
if (!parentThreadOpt.isPresent() || !replyThreadOpt.isPresent()) {
|
||||||
|
response.add("message", "Thread not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumThread replyThread = replyThreadOpt.get();
|
||||||
|
ForumThread parentThread = parentThreadOpt.get();
|
||||||
|
|
||||||
|
parentThread.getReplies().remove(replyThread);
|
||||||
|
threadService.deleteThread(replyThread);
|
||||||
|
threadService.saveThread(parentThread);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(replyThread.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getThread(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumThread> threadOpt = threadService.getThread(id);
|
||||||
|
if (!threadOpt.isPresent()) {
|
||||||
|
response.add("message", "Thread not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(threadOpt.get().toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/forum/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getForumThreads(@PathVariable(name = "id") String forumId,
|
||||||
|
@RequestParam(name = "page", defaultValue = "1") int page) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumModel> forumOpt = api.getForumService().getForumModelService()
|
||||||
|
.getByIdOrName(forumId.replace("-", " "));
|
||||||
|
if (!forumOpt.isPresent()) {
|
||||||
|
response.add("message", "Forum not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumModel forum = forumOpt.get();
|
||||||
|
|
||||||
|
List<ForumThread> threads = threadService.getForumThreads(forum.getId(), page);
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
|
||||||
|
threads.forEach(thread -> array.add(thread.toJson()));
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{parentId}/reply")
|
||||||
|
public ResponseEntity<JsonElement> createReply(@RequestBody JsonObject body, @PathVariable(name = "parentId") String parentId) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String id = body.get("id").getAsString();
|
||||||
|
Optional<ForumThread> threadOpt = threadService.getThread(id);
|
||||||
|
if (threadOpt.isPresent()) {
|
||||||
|
response.add("message", "Thread already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<ForumThread> parentOpt = threadService.getThread(parentId);
|
||||||
|
if (!parentOpt.isPresent()) {
|
||||||
|
response.add("message", "Parent thread not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumThread parent = parentOpt.get();
|
||||||
|
ForumThread thread = new ForumThread();
|
||||||
|
|
||||||
|
thread.setId(id);
|
||||||
|
thread.setParentThreadId(parent.getId());
|
||||||
|
thread.setTitle(body.get("title").getAsString());
|
||||||
|
thread.setBody(body.get("body").getAsString());
|
||||||
|
thread.setForum(body.get("forumId").getAsString());
|
||||||
|
thread.setAuthor(UUID.fromString(body.get("author").getAsString()));
|
||||||
|
thread.setCreatedAt(System.currentTimeMillis());
|
||||||
|
thread.setLastEditedBy(null);
|
||||||
|
thread.setLastEditedAt(-1L);
|
||||||
|
thread.setLastReplyAt(-1L);
|
||||||
|
thread.setPinned(false);
|
||||||
|
thread.setLocked(false);
|
||||||
|
|
||||||
|
parent.setLastReplyAt(System.currentTimeMillis());
|
||||||
|
parent.getReplies().add(thread);
|
||||||
|
|
||||||
|
threadService.saveThread(thread);
|
||||||
|
threadService.saveThread(parent);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(thread.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package org.hcrival.api.forum.thread;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ThreadService {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<String, ForumThread> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<String, ForumThread>() {
|
||||||
|
@Override
|
||||||
|
public ForumThread load(String id) throws DataNotFoundException {
|
||||||
|
Document document = master.getMongoService().getForumThreads()
|
||||||
|
.find(Filters.eq("id", id)).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new ForumThread(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<ForumThread> getThread(String id) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveThread(ForumThread thread) {
|
||||||
|
master.getMongoService().getForumThreads().replaceOne(
|
||||||
|
Filters.eq("id", thread.getId()),
|
||||||
|
thread.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(thread.getId(), thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteThread(ForumThread thread) {
|
||||||
|
master.getMongoService().getForumThreads().deleteOne(Filters.or(
|
||||||
|
Filters.eq("id", thread.getId()),
|
||||||
|
Filters.eq("parentThreadId", thread.getId())
|
||||||
|
));
|
||||||
|
cache.asMap().remove(thread.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumThread> getProfileThreads(UUID uuid, int page) {
|
||||||
|
List<ForumThread> threads = new ArrayList<>();
|
||||||
|
master.getMongoService().getForumThreads().find(Filters.and(
|
||||||
|
Filters.eq("parentThreadId", null),
|
||||||
|
Filters.eq("author", uuid.toString())))
|
||||||
|
.forEach((Block<? super Document>) document -> threads.add(new ForumThread(document)));
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumThread> getForumThreads(String forumId, int page) {
|
||||||
|
List<ForumThread> threads = new ArrayList<>();
|
||||||
|
master.getMongoService().getForumThreads().find(Filters.and(
|
||||||
|
Filters.eq("parentThreadId", null),
|
||||||
|
Filters.eq("forum", forumId)
|
||||||
|
)).forEach((Block<? super Document>) document -> threads.add(new ForumThread(document)));
|
||||||
|
return threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package org.hcrival.api.forum.ticket;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ForumTicket {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String category;
|
||||||
|
private String body;
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private UUID author;
|
||||||
|
private long createdAt;
|
||||||
|
|
||||||
|
private long lastUpdatedAt;
|
||||||
|
private transient List<TicketReply> replies = new ArrayList<>();
|
||||||
|
|
||||||
|
public ForumTicket(Document document) {
|
||||||
|
this.id = document.getString("id");
|
||||||
|
this.category = document.getString("category");
|
||||||
|
this.body = document.getString("body");
|
||||||
|
this.author = UUID.fromString(document.getString("author"));
|
||||||
|
this.createdAt = document.getLong("createdAt");
|
||||||
|
this.lastUpdatedAt = document.getLong("lastUpdatedAt");
|
||||||
|
this.status = document.getString("status");
|
||||||
|
|
||||||
|
document.getList("replies", Document.class)
|
||||||
|
.forEach(replyDocument -> replies.add(new TicketReply(replyDocument)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id);
|
||||||
|
document.append("category", category);
|
||||||
|
document.append("body", body);
|
||||||
|
document.append("author", author.toString());
|
||||||
|
document.append("createdAt", createdAt);
|
||||||
|
document.append("lastUpdatedAt", lastUpdatedAt);
|
||||||
|
document.append("status", status);
|
||||||
|
|
||||||
|
List<Document> documents = new ArrayList<>();
|
||||||
|
replies.forEach(reply -> documents.add(reply.toBson()));
|
||||||
|
document.append("replies", documents);
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
|
||||||
|
Profile profile = InvictusAPI.getInstance().getProfileService().getProfile(author).orElse(null);
|
||||||
|
if (profile != null) {
|
||||||
|
object.addProperty("authorName", profile.getName());
|
||||||
|
object.addProperty("authorWebColor", profile.getRealCurrentGrant().asRank().getWebColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
replies.forEach(reply -> array.add(reply.toJson()));
|
||||||
|
object.add("replies", array);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
package org.hcrival.api.forum.ticket;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/forum/ticket")
|
||||||
|
public class TicketController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final TicketService ticketService;
|
||||||
|
|
||||||
|
public TicketController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.ticketService = api.getForumService().getTicketService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createTicket(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String id = body.get("id").getAsString();
|
||||||
|
Optional<ForumTicket> ticketOpt = ticketService.getTicket(id);
|
||||||
|
if (ticketOpt.isPresent()) {
|
||||||
|
response.add("message", "Ticket already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumTicket ticket = new ForumTicket();
|
||||||
|
ticket.setId(id);
|
||||||
|
ticket.setCategory(body.get("category").getAsString());
|
||||||
|
ticket.setBody(body.get("body").getAsString());
|
||||||
|
ticket.setStatus(body.get("status").getAsString());
|
||||||
|
ticket.setAuthor(UUID.fromString(body.get("author").getAsString()));
|
||||||
|
ticket.setCreatedAt(System.currentTimeMillis());
|
||||||
|
ticket.setLastUpdatedAt(System.currentTimeMillis());
|
||||||
|
|
||||||
|
ticketService.saveTicket(ticket);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(ticket.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> editTicket(@RequestBody JsonObject body, @PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumTicket> ticketOpt = ticketService.getTicket(id);
|
||||||
|
if (!ticketOpt.isPresent()) {
|
||||||
|
response.add("message", "Ticket not found.");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumTicket ticket = ticketOpt.get();
|
||||||
|
|
||||||
|
if (body.has("category"))
|
||||||
|
ticket.setCategory(body.get("title").getAsString());
|
||||||
|
|
||||||
|
if (body.has("body"))
|
||||||
|
ticket.setBody(body.get("body").getAsString());
|
||||||
|
|
||||||
|
if (body.has("status"))
|
||||||
|
ticket.setStatus(body.get("status").getAsString());
|
||||||
|
|
||||||
|
ticket.setLastUpdatedAt(System.currentTimeMillis());
|
||||||
|
|
||||||
|
ticketService.saveTicket(ticket);
|
||||||
|
return new ResponseEntity<>(ticket.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getTicket(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumTicket> ticketOpt = ticketService.getTicket(id);
|
||||||
|
if (!ticketOpt.isPresent()) {
|
||||||
|
response.add("message", "Ticket not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(ticketOpt.get().toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/player/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getPlayerTickets(@PathVariable(name = "uuid") UUID uuid,
|
||||||
|
@RequestParam(name = "page", defaultValue = "1") int page) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
|
||||||
|
List<ForumTicket> tickets = ticketService.getPlayerTickets(uuid, page);
|
||||||
|
tickets.forEach(ticket -> array.add(ticket.toJson()));
|
||||||
|
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// makes this /forum/tickets/all, clashing with /forum/ticket/{id} otherwise
|
||||||
|
@GetMapping(path = "/admin")
|
||||||
|
public ResponseEntity<JsonElement> getAllTickets(@RequestParam(name = "page", defaultValue = "1") int page) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
|
||||||
|
List<ForumTicket> tickets = ticketService.getAllTickets(page);
|
||||||
|
tickets.forEach(ticket -> array.add(ticket.toJson()));
|
||||||
|
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{parentId}/reply")
|
||||||
|
public ResponseEntity<JsonElement> createReply(@RequestBody JsonObject body, @PathVariable(name = "parentId") String parentId) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<ForumTicket> parentOpt = ticketService.getTicket(parentId);
|
||||||
|
if (!parentOpt.isPresent()) {
|
||||||
|
response.add("message", "Parent ticket not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForumTicket parent = parentOpt.get();
|
||||||
|
TicketReply reply = new TicketReply();
|
||||||
|
|
||||||
|
reply.setId(body.get("id").getAsString());
|
||||||
|
reply.setBody(body.get("body").getAsString());
|
||||||
|
reply.setAuthor(UUID.fromString(body.get("author").getAsString()));
|
||||||
|
reply.setCreatedAt(System.currentTimeMillis());
|
||||||
|
reply.setParentTicketId(parent.getId());
|
||||||
|
|
||||||
|
parent.setLastUpdatedAt(System.currentTimeMillis());
|
||||||
|
parent.getReplies().add(reply);
|
||||||
|
|
||||||
|
ticketService.saveTicket(parent);
|
||||||
|
return new ResponseEntity<>(reply.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package org.hcrival.api.forum.ticket;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TicketReply {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String body;
|
||||||
|
private UUID author;
|
||||||
|
private long createdAt;
|
||||||
|
private String parentTicketId;
|
||||||
|
|
||||||
|
public TicketReply(Document document) {
|
||||||
|
this.id = document.getString("id");
|
||||||
|
this.body = document.getString("body");
|
||||||
|
this.author = UUID.fromString(document.getString("author"));
|
||||||
|
this.createdAt = document.getLong("createdAt");
|
||||||
|
this.parentTicketId = document.getString("parentTicketId");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id);
|
||||||
|
document.append("body", body);
|
||||||
|
document.append("author", author.toString());
|
||||||
|
document.append("createdAt", createdAt);
|
||||||
|
document.append("parentTicketId", parentTicketId);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
|
||||||
|
Profile profile = InvictusAPI.getInstance().getProfileService().getProfile(author).orElse(null);
|
||||||
|
if (profile != null) {
|
||||||
|
object.addProperty("authorName", profile.getName());
|
||||||
|
object.addProperty("authorWebColor", profile.getRealCurrentGrant().asRank().getWebColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package org.hcrival.api.forum.ticket;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TicketService {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<String, ForumTicket> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<String, ForumTicket>() {
|
||||||
|
@Override
|
||||||
|
public ForumTicket load(String id) throws DataNotFoundException {
|
||||||
|
Document document = master.getMongoService().getForumTickets()
|
||||||
|
.find(Filters.eq("id", id)).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new ForumTicket(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<ForumTicket> getTicket(String id) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveTicket(ForumTicket ticket) {
|
||||||
|
master.getMongoService().getForumTickets().replaceOne(
|
||||||
|
Filters.eq("id", ticket.getId()),
|
||||||
|
ticket.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(ticket.getId(), ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTicket(ForumTicket ticket) {
|
||||||
|
master.getMongoService().getForumTickets().deleteOne(Filters.eq("id", ticket.getId()));
|
||||||
|
cache.asMap().remove(ticket.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumTicket> getPlayerTickets(UUID uuid, int page) {
|
||||||
|
List<ForumTicket> tickets = new ArrayList<>();
|
||||||
|
master.getMongoService().getForumTickets().find(Filters.eq("author", uuid.toString()))
|
||||||
|
.forEach((Block<? super Document>) document -> tickets.add(new ForumTicket(document)));
|
||||||
|
return tickets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ForumTicket> getAllTickets(int page) {
|
||||||
|
List<ForumTicket> tickets = new ArrayList<>();
|
||||||
|
master.getMongoService().getForumTickets().find()
|
||||||
|
.forEach((Block<? super Document>) document -> tickets.add(new ForumTicket(document)));
|
||||||
|
return tickets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package org.hcrival.api.forum.trophy;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/forum/trophy")
|
||||||
|
public class TrophyController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final TrophyService trophyService;
|
||||||
|
|
||||||
|
public TrophyController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.trophyService = api.getForumService().getTrophyService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> listAll() {
|
||||||
|
JsonArray response = new JsonArray();
|
||||||
|
trophyService.getTrophies().forEach(trophy -> response.add(trophy.toJson()));
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{id}")
|
||||||
|
public ResponseEntity deleteTrophy(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<TrophyModel> trophyOpt = trophyService.getById(id);
|
||||||
|
if (!trophyOpt.isPresent()) {
|
||||||
|
response.add("message", "Trophy not found.");
|
||||||
|
return new ResponseEntity(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrophyModel trophy = trophyOpt.get();
|
||||||
|
trophyService.deleteTrophy(trophy);
|
||||||
|
|
||||||
|
return new ResponseEntity(trophy.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping()
|
||||||
|
public ResponseEntity<JsonElement> createTrophy(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
String id = body.get("id").getAsString();
|
||||||
|
Optional<TrophyModel> trophyOpt = trophyService.getById(id);
|
||||||
|
if (trophyOpt.isPresent()) {
|
||||||
|
response.add("message", "Trophy already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrophyModel trophyModel = new TrophyModel();
|
||||||
|
trophyModel.setId(id);
|
||||||
|
trophyModel.setName(body.get("name").getAsString());
|
||||||
|
|
||||||
|
trophyService.saveTrophy(trophyModel);
|
||||||
|
return new ResponseEntity<>(trophyModel.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getTrophy(@PathVariable(name = "id") String id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<TrophyModel> trophyOpt = trophyService.getById(id);
|
||||||
|
|
||||||
|
if (!trophyOpt.isPresent()) {
|
||||||
|
response.add("message", "Trophy not found.");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(trophyOpt.get().toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package org.hcrival.api.forum.trophy;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class TrophyModel {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public TrophyModel(Document document) {
|
||||||
|
this.id = document.getString("id");
|
||||||
|
this.name = document.getString("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
|
||||||
|
document.append("id", id);
|
||||||
|
document.append("name", name);
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.hcrival.api.forum.trophy;
|
||||||
|
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TrophyService {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
private final Map<String, TrophyModel> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loadTrophies() {
|
||||||
|
master.getMongoService().getForumTrophies().find().forEach((Block<? super Document>) document -> {
|
||||||
|
TrophyModel trophy = new TrophyModel(document);
|
||||||
|
cache.put(trophy.getId(), trophy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Optional<TrophyModel> getById(String id) {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TrophyModel> getTrophies() {
|
||||||
|
return new ArrayList<>(cache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveTrophy(TrophyModel trophy) {
|
||||||
|
master.getMongoService().getForumTrophies().replaceOne(
|
||||||
|
Filters.eq("id", trophy.getId()),
|
||||||
|
trophy.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(trophy.getId(), trophy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTrophy(TrophyModel trophy) {
|
||||||
|
master.getMongoService().getForumTrophies().deleteOne(Filters.eq("id", trophy.getId()));
|
||||||
|
cache.remove(trophy.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
90
API/src/main/java/org/hcrival/api/mongo/MongoService.java
Normal file
90
API/src/main/java/org/hcrival/api/mongo/MongoService.java
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package org.hcrival.api.mongo;
|
||||||
|
|
||||||
|
import com.mongodb.MongoClient;
|
||||||
|
import com.mongodb.MongoCredential;
|
||||||
|
import com.mongodb.ServerAddress;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import com.mongodb.client.model.ReplaceOptions;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.configuration.defaults.MongoConfig;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MongoService {
|
||||||
|
|
||||||
|
public static final ReplaceOptions REPLACE_OPTIONS = new ReplaceOptions().upsert(true);
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
private MongoClient client;
|
||||||
|
|
||||||
|
// Invictus
|
||||||
|
|
||||||
|
private MongoDatabase invictusDatabase;
|
||||||
|
private MongoDatabase forumsDatabase;
|
||||||
|
|
||||||
|
private MongoCollection<Document> ranks;
|
||||||
|
private MongoCollection<Document> tags;
|
||||||
|
private MongoCollection<Document> punishments;
|
||||||
|
private MongoCollection<Document> profiles;
|
||||||
|
private MongoCollection<Document> grants;
|
||||||
|
private MongoCollection<Document> notes;
|
||||||
|
private MongoCollection<Document> disguiseData;
|
||||||
|
private MongoCollection<Document> discordData;
|
||||||
|
private MongoCollection<Document> banphrases;
|
||||||
|
|
||||||
|
private MongoCollection<Document> forumAccounts;
|
||||||
|
private MongoCollection<Document> forumCategories;
|
||||||
|
private MongoCollection<Document> forumForums;
|
||||||
|
private MongoCollection<Document> forumThreads;
|
||||||
|
private MongoCollection<Document> forumTickets;
|
||||||
|
private MongoCollection<Document> forumTrophies;
|
||||||
|
|
||||||
|
public boolean connect() {
|
||||||
|
MongoConfig config = master.getMainConfig().getMongoConfig();
|
||||||
|
if (config.isAuthEnabled()) {
|
||||||
|
MongoCredential credential = MongoCredential.createCredential(
|
||||||
|
config.getAuthUsername(),
|
||||||
|
config.getAuthDatabase(),
|
||||||
|
config.getAuthPassword().toCharArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
client = new MongoClient(
|
||||||
|
new ServerAddress(config.getHost(), config.getPort()),
|
||||||
|
Collections.singletonList(credential)
|
||||||
|
);
|
||||||
|
} else client = new MongoClient(config.getHost(), config.getPort());
|
||||||
|
|
||||||
|
try {
|
||||||
|
invictusDatabase = client.getDatabase("invictus");
|
||||||
|
ranks = invictusDatabase.getCollection("ranks");
|
||||||
|
tags = invictusDatabase.getCollection("tags");
|
||||||
|
punishments = invictusDatabase.getCollection("punishments");
|
||||||
|
profiles = invictusDatabase.getCollection("profiles");
|
||||||
|
grants = invictusDatabase.getCollection("grants");
|
||||||
|
notes = invictusDatabase.getCollection("notes");
|
||||||
|
disguiseData = invictusDatabase.getCollection("disguiseData");
|
||||||
|
discordData = invictusDatabase.getCollection("discordData");
|
||||||
|
banphrases = invictusDatabase.getCollection("banphrases");
|
||||||
|
|
||||||
|
forumsDatabase = client.getDatabase("forums");
|
||||||
|
forumAccounts = forumsDatabase.getCollection("accounts");
|
||||||
|
forumCategories = forumsDatabase.getCollection("categories");
|
||||||
|
forumForums = forumsDatabase.getCollection("forums");
|
||||||
|
forumThreads = forumsDatabase.getCollection("threads");
|
||||||
|
forumTickets = forumsDatabase.getCollection("tickets");
|
||||||
|
forumTrophies = forumsDatabase.getCollection("trophies");
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
148
API/src/main/java/org/hcrival/api/profile/Profile.java
Normal file
148
API/src/main/java/org/hcrival/api/profile/Profile.java
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package org.hcrival.api.profile;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.profile.grant.Grant;
|
||||||
|
import org.hcrival.api.profile.grant.GrantService;
|
||||||
|
import org.hcrival.api.punishment.Punishment;
|
||||||
|
import org.hcrival.api.punishment.PunishmentService;
|
||||||
|
import org.hcrival.api.rank.RankService;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Profile {
|
||||||
|
|
||||||
|
private static final GrantService GRANT_SERVICE = InvictusAPI.getInstance().getGrantService();
|
||||||
|
private static final PunishmentService PUNISHMENT_SERVICE = InvictusAPI.getInstance().getPunishmentService();
|
||||||
|
private static final RankService RANK_SERVICE = InvictusAPI.getInstance().getRankService();
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String name = "N/A";
|
||||||
|
private String lastIp = "N/A";
|
||||||
|
private List<String> knownIps = new ArrayList<>();
|
||||||
|
private ProfileOptions options = new ProfileOptions();
|
||||||
|
private List<String> permissions = new ArrayList<>();
|
||||||
|
private long firstLogin = -1;
|
||||||
|
private long lastSeen = -1;
|
||||||
|
private long joinTime = -1;
|
||||||
|
private long playTime = -1;
|
||||||
|
private String lastServer = null;
|
||||||
|
private String activeTag = null;
|
||||||
|
|
||||||
|
public Profile(Document document) {
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.lastIp = document.getString("lastIp");
|
||||||
|
this.knownIps = document.getList("knownIps", String.class);
|
||||||
|
this.options = new ProfileOptions(document.get("options", Document.class));
|
||||||
|
this.permissions = document.getList("permissions", String.class);
|
||||||
|
this.firstLogin = document.get("firstLogin", Number.class).longValue();
|
||||||
|
this.lastSeen = document.get("lastSeen", Number.class).longValue();
|
||||||
|
this.joinTime = document.get("joinTime", Number.class).longValue();
|
||||||
|
this.playTime = document.get("playTime", Number.class).longValue();
|
||||||
|
this.lastServer = document.getString("lastServer");
|
||||||
|
this.activeTag = document.getString("lastTag");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("lastIp", lastIp);
|
||||||
|
document.append("knownIps", knownIps);
|
||||||
|
document.append("options", options.toBson());
|
||||||
|
document.append("permissions", permissions);
|
||||||
|
document.append("firstLogin", firstLogin);
|
||||||
|
document.append("lastSeen", lastSeen);
|
||||||
|
document.append("joinTime", joinTime);
|
||||||
|
document.append("playTime", playTime);
|
||||||
|
document.append("lastServer", lastServer);
|
||||||
|
document.append("activeTag", activeTag);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
|
||||||
|
JsonArray grants = new JsonArray();
|
||||||
|
getActiveGrants().forEach(grant -> grants.add(grant.toJson()));
|
||||||
|
object.add("activeGrants", grants);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Grant getRealCurrentGrant() {
|
||||||
|
Grant grant = null;
|
||||||
|
|
||||||
|
for (Grant current : this.getActiveGrants()) {
|
||||||
|
if (grant == null) {
|
||||||
|
grant = current;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.asRank().getWeight() > grant.asRank().getWeight())
|
||||||
|
grant = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return grant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Grant> getActiveGrants() {
|
||||||
|
List<Grant> activeGrants = GRANT_SERVICE.getGrantsOf(uuid).stream()
|
||||||
|
.filter(grant -> !grant.isRemoved() && grant.isActive() && grant.asRank() != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (activeGrants.isEmpty()) {
|
||||||
|
Grant grant = new Grant();
|
||||||
|
grant.setId(UUID.randomUUID());
|
||||||
|
grant.setUuid(uuid);
|
||||||
|
grant.setRank(RANK_SERVICE.getDefaultRank().getUuid());
|
||||||
|
grant.setGrantedAt(System.currentTimeMillis());
|
||||||
|
grant.setGrantedReason("Default Grant");
|
||||||
|
grant.setGrantedBy("Console");
|
||||||
|
grant.setScopes("GLOBAL");
|
||||||
|
grant.setDuration(-1);
|
||||||
|
grant.setEnd(-1);
|
||||||
|
GRANT_SERVICE.saveGrant(grant);
|
||||||
|
return getActiveGrants();
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeGrants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Grant> getActiveGrantsOn(String scope) {
|
||||||
|
return getActiveGrants().stream()
|
||||||
|
.filter(grant -> grant.isActiveOnScope(scope))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasGrantOf(String rank) {
|
||||||
|
return getActiveGrants().stream()
|
||||||
|
.anyMatch(grant -> grant.asRank().getName().equalsIgnoreCase(rank));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasGrantOfOn(String rank, String scope) {
|
||||||
|
return getActiveGrantsOn(scope).stream()
|
||||||
|
.anyMatch(grant -> grant.asRank().getName().equalsIgnoreCase(rank));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Punishment> getActivePunishment(String type) {
|
||||||
|
return PUNISHMENT_SERVICE.getPunishmentsOf(uuid).stream()
|
||||||
|
.filter(punishment -> punishment.isActive()
|
||||||
|
&& !punishment.isRemoved()
|
||||||
|
&& punishment.getPunishmentType().equals(type))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return getRealCurrentGrant().asRank().getColor() + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
268
API/src/main/java/org/hcrival/api/profile/ProfileController.java
Normal file
268
API/src/main/java/org/hcrival/api/profile/ProfileController.java
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
package org.hcrival.api.profile;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.discord.DiscordData;
|
||||||
|
import org.hcrival.api.forum.account.ForumAccount;
|
||||||
|
import org.hcrival.api.punishment.Punishment;
|
||||||
|
import org.hcrival.api.rank.Rank;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.PermissionUtil;
|
||||||
|
import org.hcrival.api.util.UUIDCache;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/profile")
|
||||||
|
public class ProfileController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final ProfileService profileService;
|
||||||
|
|
||||||
|
public ProfileController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.profileService = api.getProfileService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{uuid}/login")
|
||||||
|
public ResponseEntity<JsonElement> profileLogin(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
String name = body.get("name").getAsString();
|
||||||
|
String ip = body.get("ip").getAsString();
|
||||||
|
long timeStamp = body.get("timeStamp").getAsLong();
|
||||||
|
String server = body.get("server").getAsString();
|
||||||
|
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(uuid);
|
||||||
|
Profile profile;
|
||||||
|
if (profileOpt.isPresent())
|
||||||
|
profile = profileOpt.get();
|
||||||
|
else {
|
||||||
|
profile = new Profile();
|
||||||
|
profile.setUuid(uuid);
|
||||||
|
profile.setName(name);
|
||||||
|
profile.setFirstLogin(timeStamp);
|
||||||
|
// api.getStatsTracker().addRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.getLastIp() == null)
|
||||||
|
profile.setLastIp(ip);
|
||||||
|
|
||||||
|
Optional<Punishment> activePunishment = profile.getActivePunishment("BLACKLIST");
|
||||||
|
if (!activePunishment.isPresent())
|
||||||
|
activePunishment = profile.getActivePunishment("BAN");
|
||||||
|
|
||||||
|
if (!activePunishment.isPresent()) {
|
||||||
|
Optional<Punishment> evasionPunishment = profileService.getAlts(profile).stream()
|
||||||
|
.filter(alt -> alt.getLastIp() != null
|
||||||
|
&& profile.getLastIp() != null
|
||||||
|
&& alt.getLastIp().equals(profile.getLastIp())
|
||||||
|
&& alt.getActivePunishment("BLACKLIST").isPresent()).findFirst()
|
||||||
|
.flatMap(alt -> alt.getActivePunishment("BLACKLIST"));
|
||||||
|
|
||||||
|
if (!evasionPunishment.isPresent())
|
||||||
|
evasionPunishment = profileService.getAlts(profile).stream()
|
||||||
|
.filter(alt -> alt.getLastIp() != null
|
||||||
|
&& profile.getLastIp() != null
|
||||||
|
&& alt.getLastIp().equals(profile.getLastIp())
|
||||||
|
&& alt.getActivePunishment("BAN").isPresent()).findFirst()
|
||||||
|
.flatMap(alt -> alt.getActivePunishment("BAN"));
|
||||||
|
|
||||||
|
if (evasionPunishment.isPresent()
|
||||||
|
&& !profile.hasGrantOfOn("evasion-bypass", body.get("grantScope").getAsString())) {
|
||||||
|
Punishment punishment = new Punishment();
|
||||||
|
punishment.setId(UUID.randomUUID());
|
||||||
|
punishment.setUuid(profile.getUuid());
|
||||||
|
punishment.setPunishedBy("Console");
|
||||||
|
punishment.setPunishmentType(evasionPunishment.get().getPunishmentType());
|
||||||
|
punishment.setPunishedServerType("Server");
|
||||||
|
punishment.setPunishedServer(server);
|
||||||
|
punishment.setPunishedAt(System.currentTimeMillis());
|
||||||
|
|
||||||
|
Profile alt = profileService.getProfile(evasionPunishment.get().getUuid()).orElse(null);
|
||||||
|
punishment.setPunishedReason(String.format("%s Evading (%s)",
|
||||||
|
evasionPunishment.get().getPunishmentType().equals("BLACKLIST") ? "Blacklist" : "Ban",
|
||||||
|
alt != null ? alt.getName() : "Unknown Player"
|
||||||
|
));
|
||||||
|
|
||||||
|
long duration = evasionPunishment.get().getEnd() == -1 ? -1
|
||||||
|
: evasionPunishment.get().getEnd() - System.currentTimeMillis();
|
||||||
|
punishment.setDuration(duration);
|
||||||
|
punishment.setEnd(evasionPunishment.get().getEnd());
|
||||||
|
|
||||||
|
api.getPunishmentService().savePunishment(punishment);
|
||||||
|
|
||||||
|
activePunishment = Optional.of(punishment);
|
||||||
|
response.add("evasionPunishment", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
activePunishment.ifPresent(punishment -> {
|
||||||
|
response.add("activePunishment", punishment.toJson());
|
||||||
|
if (!profile.getLastIp().equals(ip))
|
||||||
|
profile.setLastIp(ip);
|
||||||
|
if (!profile.getKnownIps().contains(ip))
|
||||||
|
profile.getKnownIps().add(ip);
|
||||||
|
});
|
||||||
|
|
||||||
|
Optional<DiscordData> discordData = api.getDiscordService().getByUuid(uuid);
|
||||||
|
response.add("boosted", discordData.isPresent() && discordData.get().isBoosted());
|
||||||
|
|
||||||
|
profileService.saveProfile(profile);
|
||||||
|
|
||||||
|
response.add("isOnVPN", false/*antiVPNService.isVPN(ip)*/);
|
||||||
|
|
||||||
|
response.add("profile", profile.toJson());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{uuid}/join")
|
||||||
|
public ResponseEntity<JsonElement> profileJoin(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(uuid);
|
||||||
|
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profile = profileOpt.get();
|
||||||
|
String ip = body.get("ip").getAsString();
|
||||||
|
boolean staff = body.get("staff").getAsBoolean();
|
||||||
|
String server = body.get("server").getAsString();
|
||||||
|
long timeStamp = body.get("timeStamp").getAsLong();
|
||||||
|
|
||||||
|
response.add("lastServer", profile.getLastServer());
|
||||||
|
|
||||||
|
if (!profile.getLastIp().equals(ip)) {
|
||||||
|
if (staff)
|
||||||
|
response.add("requiresTotp", true);
|
||||||
|
else profile.setLastIp(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profile.getKnownIps().contains(ip))
|
||||||
|
profile.getKnownIps().add(ip);
|
||||||
|
|
||||||
|
profile.setLastServer(server);
|
||||||
|
if (profile.getFirstLogin() == -1)
|
||||||
|
profile.setFirstLogin(timeStamp);
|
||||||
|
profile.setLastSeen(timeStamp);
|
||||||
|
profile.setJoinTime(timeStamp);
|
||||||
|
profileService.saveProfile(profile);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getProfile(@PathVariable(name = "uuid") UUID uuid,
|
||||||
|
@RequestParam(name = "webResolved", defaultValue = "false") boolean web,
|
||||||
|
@RequestParam(name = "includePermissions", defaultValue = "false") boolean perms) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(uuid);
|
||||||
|
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profileObj = profileOpt.get();
|
||||||
|
JsonObject profile = profileObj.toJson();
|
||||||
|
|
||||||
|
if (web) {
|
||||||
|
JsonArray activeGrants = new JsonArray();
|
||||||
|
api.getGrantService().getGrantsOf(profileObj.getUuid()).forEach(grant -> activeGrants.add(grant.toJson()));
|
||||||
|
|
||||||
|
for (JsonElement element : activeGrants) {
|
||||||
|
JsonObject grant = element.getAsJsonObject();
|
||||||
|
String grantedBy = grant.get("grantedBy").getAsString();
|
||||||
|
grant.addProperty("resolvedGrantedBy", UUIDCache.isUuid(grantedBy)
|
||||||
|
? UUIDCache.getName(UUID.fromString(grantedBy))
|
||||||
|
: grantedBy);
|
||||||
|
|
||||||
|
if (grant.get("removedBy") != null) {
|
||||||
|
String removedBy = grant.get("removedBy").getAsString();
|
||||||
|
grant.addProperty("resolvedRemovedBy", UUIDCache.isUuid(removedBy)
|
||||||
|
? UUIDCache.getName(UUID.fromString(removedBy))
|
||||||
|
: removedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Rank> rankOpt = api.getRankService().getRank(UUID.fromString(grant.get("rank").getAsString()));
|
||||||
|
grant.addProperty("resolvedRank", rankOpt.isPresent() ? rankOpt.get().getName() : "???");
|
||||||
|
grant.addProperty("webColor", rankOpt.isPresent() ? rankOpt.get().getWebColor() : "#FFFFFF");
|
||||||
|
}
|
||||||
|
|
||||||
|
Rank rank = profileObj.getRealCurrentGrant().asRank();
|
||||||
|
JsonObject rankObject = rank.toJson();
|
||||||
|
rankObject.addProperty("webColor", rank.getWebColor());
|
||||||
|
profile.add("rank", rankObject);
|
||||||
|
profile.add("activeGrants", activeGrants);
|
||||||
|
|
||||||
|
JsonObject settings = new JsonObject();
|
||||||
|
|
||||||
|
Optional<ForumAccount> accountOpt = api.getForumService().getAccountService().getAccount(profileObj.getUuid());
|
||||||
|
if (accountOpt.isPresent()) {
|
||||||
|
ForumAccount account = accountOpt.get();
|
||||||
|
account.getSettings().forEach(settings::addProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.add("webSettings", settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perms) {
|
||||||
|
JsonObject permissions = PermissionUtil.getEffectivePermissions(profileObj);
|
||||||
|
profile.add("effectivePermissions", permissions);
|
||||||
|
profile.addProperty("isOnOplist", api.getMainConfig().getOpList().contains(profileObj.getUuid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(profile, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createProfile(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Profile profile = JsonConfigurationService.gson.fromJson(body, Profile.class);
|
||||||
|
if (profileService.getProfile(profile.getUuid()).isPresent()) {
|
||||||
|
response.add("message", "Profile already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
profileService.saveProfile(profile);
|
||||||
|
// api.getStatsTracker().addRegistration();
|
||||||
|
return new ResponseEntity<>(profile.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
public ResponseEntity<JsonElement> updateProfile(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Profile profile = JsonConfigurationService.gson.fromJson(body, Profile.class);
|
||||||
|
if (!profileService.getProfile(profile.getUuid()).isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
profileService.saveProfile(profile);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{uuid}/alts")
|
||||||
|
public ResponseEntity<JsonElement> getAlts(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(uuid);
|
||||||
|
|
||||||
|
if (!profileOpt.isPresent())
|
||||||
|
return new ResponseEntity<>(new JsonArray(), HttpStatus.OK);
|
||||||
|
|
||||||
|
Profile profile = profileOpt.get();
|
||||||
|
JsonArray alts = new JsonArray();
|
||||||
|
profileService.getAlts(profile).forEach(alt -> alts.add(alt.toJson()));
|
||||||
|
return new ResponseEntity<>(alts, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.hcrival.api.profile;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ProfileOptions {
|
||||||
|
|
||||||
|
private List<String> socialSpy = new ArrayList<>();
|
||||||
|
private List<UUID> ignoring = new ArrayList<>();
|
||||||
|
private Map<String, String> customOptions = new HashMap<>();
|
||||||
|
|
||||||
|
public ProfileOptions(Document document) {
|
||||||
|
this.socialSpy = document.getList("socialSpy", String.class);
|
||||||
|
this.ignoring = document.getList("ignoring", String.class).stream()
|
||||||
|
.map(UUID::fromString)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
document.get("customOptions", Document.class)
|
||||||
|
.forEach((key, value) -> customOptions.put(key, String.valueOf(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("socialSpy", socialSpy);
|
||||||
|
document.append("ignoring", ignoring.stream()
|
||||||
|
.map(UUID::toString)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
Document customOptionsDocument = new Document();
|
||||||
|
customOptions.forEach(customOptionsDocument::append);
|
||||||
|
|
||||||
|
document.append("customOptions", customOptionsDocument);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package org.hcrival.api.profile;
|
||||||
|
|
||||||
|
import com.google.common.cache.*;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProfileService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, Profile> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, Profile>() {
|
||||||
|
@Override
|
||||||
|
public Profile load(UUID uuid) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getProfiles()
|
||||||
|
.find(Filters.eq("uuid", uuid.toString())).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
Profile profile = new Profile(document);
|
||||||
|
// Optional<Player> playerOpt = api.getPlayerService().getPlayer(profile.getUuid());
|
||||||
|
// if (playerOpt.isPresent()) {
|
||||||
|
// Player player = playerOpt.get();
|
||||||
|
// player.setHandle(profile);
|
||||||
|
// player.reloadPermissions();
|
||||||
|
// }
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<Profile> getProfile(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(uuid));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveProfile(Profile profile) {
|
||||||
|
api.getMongoService().getProfiles().replaceOne(
|
||||||
|
Filters.eq("uuid", profile.getUuid().toString()),
|
||||||
|
profile.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
// Optional<Player> playerOpt = api.getPlayerService().getPlayer(profile.getUuid());
|
||||||
|
// if (playerOpt.isPresent()) {
|
||||||
|
// Player player = playerOpt.get();
|
||||||
|
// player.setHandle(profile);
|
||||||
|
// player.reloadPermissions();
|
||||||
|
// }
|
||||||
|
|
||||||
|
cache.put(profile.getUuid(), profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Profile> getAlts(Profile profile) {
|
||||||
|
List<Profile> toReturn = new ArrayList<>();
|
||||||
|
Set<UUID> uuidSet = new HashSet<>();
|
||||||
|
for (Document document : api.getMongoService().getProfiles()
|
||||||
|
.find(Filters.in("knownIps", profile.getKnownIps()))) {
|
||||||
|
UUID uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
if (profile.getUuid().equals(uuid) || uuidSet.contains(uuid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Profile alt = new Profile(document);
|
||||||
|
cache.put(uuid, alt);
|
||||||
|
if (!uuidSet.contains(alt.getUuid())) {
|
||||||
|
uuidSet.add(alt.getUuid());
|
||||||
|
toReturn.add(alt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
116
API/src/main/java/org/hcrival/api/profile/grant/Grant.java
Normal file
116
API/src/main/java/org/hcrival/api/profile/grant/Grant.java
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package org.hcrival.api.profile.grant;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.rank.Rank;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Grant {
|
||||||
|
|
||||||
|
public static Comparator<Grant> COMPARATOR = Comparator.comparingInt(grant -> grant.asRank().getWeight());
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
private UUID uuid;
|
||||||
|
private UUID rank;
|
||||||
|
private String grantedBy;
|
||||||
|
private long grantedAt;
|
||||||
|
private String grantedReason;
|
||||||
|
private String removedBy = "N/A";
|
||||||
|
private long removedAt = -1;
|
||||||
|
private String removedReason = "N/A";
|
||||||
|
private String scopes;
|
||||||
|
private long duration;
|
||||||
|
private long end;
|
||||||
|
private boolean removed = false;
|
||||||
|
|
||||||
|
public Grant(Document document) {
|
||||||
|
this.id = UUID.fromString(document.getString("id"));
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.rank = UUID.fromString(document.getString("rank"));
|
||||||
|
this.grantedBy = document.getString("grantedBy");
|
||||||
|
this.grantedAt = document.get("grantedAt", Number.class).longValue();
|
||||||
|
this.grantedReason = document.getString("grantedReason");
|
||||||
|
this.removedBy = document.getString("removedBy");
|
||||||
|
this.removedAt = document.get("removedAt", Number.class).longValue();
|
||||||
|
this.removedReason = document.getString("removedReason");
|
||||||
|
this.scopes = document.getString("scopes");
|
||||||
|
this.duration = document.get("duration", Number.class).longValue();
|
||||||
|
this.end = document.get("end", Number.class).longValue();
|
||||||
|
this.removed = document.getBoolean("removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id.toString());
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("rank", rank.toString());
|
||||||
|
document.append("grantedBy", grantedBy);
|
||||||
|
document.append("grantedAt", grantedAt);
|
||||||
|
document.append("grantedReason", grantedReason);
|
||||||
|
document.append("removedBy", removedBy);
|
||||||
|
document.append("removedAt", removedAt);
|
||||||
|
document.append("removedReason", removedReason);
|
||||||
|
document.append("scopes", scopes);
|
||||||
|
document.append("duration", duration);
|
||||||
|
document.append("end", end);
|
||||||
|
document.append("removed", removed);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rank asRank() {
|
||||||
|
return InvictusAPI.getInstance().getRankService().getRank(rank).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
if (end == -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return end >= System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getScopeList() {
|
||||||
|
return Arrays.asList(scopes.split(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActiveOnScope(String scope) {
|
||||||
|
if (scope.equalsIgnoreCase("GLOBAL"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
List<String> scopeList = getScopeList();
|
||||||
|
if (scopeList.contains("GLOBAL"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// ServerGroup serverGroup = Master.getInstance().getGroupService().getServerGroup(scope);
|
||||||
|
// if (serverGroup != null) {
|
||||||
|
// validScopes.addAll(serverGroup.getConfig().getAcceptingScopes());
|
||||||
|
// validScopes.add(serverGroup.getName().toLowerCase());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ProxyGroup proxyGroup = Master.getInstance().getGroupService().getProxyGroup(scope);
|
||||||
|
// if (proxyGroup != null)
|
||||||
|
// validScopes.add(proxyGroup.getName().toLowerCase());
|
||||||
|
//
|
||||||
|
// StaticServer staticServer = Master.getInstance().getGroupService().getStaticServer(scope);
|
||||||
|
// if (staticServer != null) {
|
||||||
|
// validScopes.addAll(staticServer.getAcceptingScopes());
|
||||||
|
// validScopes.add(staticServer.getName().toLowerCase());
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (scopeList.contains(scope.toLowerCase()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
package org.hcrival.api.profile.grant;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.profile.grant.rpacket.GrantAddPacket;
|
||||||
|
import org.hcrival.api.profile.grant.rpacket.GrantRemovePacket;
|
||||||
|
import org.hcrival.api.profile.rpacket.ProfileUpdatePacket;
|
||||||
|
import org.hcrival.api.rank.Rank;
|
||||||
|
import org.hcrival.api.rank.RankService;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/profile/{uuid}/grants")
|
||||||
|
public class GrantController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final GrantService grantService;
|
||||||
|
private final RankService rankService;
|
||||||
|
|
||||||
|
public GrantController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.grantService = api.getGrantService();
|
||||||
|
this.rankService = api.getRankService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> addGrant(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
UUID id = body.has("id")
|
||||||
|
? UUID.fromString(body.get("id").getAsString())
|
||||||
|
: UUID.randomUUID();
|
||||||
|
|
||||||
|
if (grantService.getGrant(id).isPresent()) {
|
||||||
|
response.add("message", "Grant already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Grant grant = new Grant();
|
||||||
|
grant.setId(id);
|
||||||
|
grant.setUuid(uuid);
|
||||||
|
grant.setRank(UUID.fromString(body.get("rank").getAsString()));
|
||||||
|
|
||||||
|
if (!rankService.getRank(grant.getRank()).isPresent()) {
|
||||||
|
response.add("message", "Rank not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
grant.setGrantedBy(body.get("grantedBy").getAsString());
|
||||||
|
grant.setGrantedAt(body.has("grantedAt")
|
||||||
|
? body.get("grantedAt").getAsLong()
|
||||||
|
: System.currentTimeMillis());
|
||||||
|
grant.setGrantedReason(body.get("grantedReason").getAsString());
|
||||||
|
grant.setRemovedBy("N/A");
|
||||||
|
grant.setRemovedAt(-1);
|
||||||
|
grant.setRemovedReason("N/A");
|
||||||
|
grant.setScopes(body.get("scopes").getAsString());
|
||||||
|
grant.setDuration(body.get("duration").getAsLong());
|
||||||
|
grant.setEnd(body.has("end")
|
||||||
|
? body.get("end").getAsLong()
|
||||||
|
: (grant.getDuration() == -1
|
||||||
|
? -1 : grant.getGrantedAt() + grant.getDuration()));
|
||||||
|
grant.setRemoved(false);
|
||||||
|
|
||||||
|
grantService.saveGrant(grant);
|
||||||
|
api.getRedisService().publish(new GrantAddPacket(grant.getUuid(), grant.getRank(), grant.getDuration()));
|
||||||
|
api.getRedisService().publish(new ProfileUpdatePacket(grant.getUuid()));
|
||||||
|
return new ResponseEntity<>(grant.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> getGrantsOf(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonArray grants = new JsonArray();
|
||||||
|
grantService.getGrantsOf(uuid).forEach(grant -> grants.add(grant.toJson()));
|
||||||
|
return new ResponseEntity<>(grants, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> updateGrant(@RequestBody JsonObject body,
|
||||||
|
@PathVariable(name = "uuid") UUID uuid,
|
||||||
|
@PathVariable(name = "id") UUID id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Grant> grantOpt = grantService.getGrant(id);
|
||||||
|
|
||||||
|
if (!grantOpt.isPresent()) {
|
||||||
|
response.add("message", "Grant not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Grant grant = grantOpt.get();
|
||||||
|
|
||||||
|
if (body.has("grantedReason"))
|
||||||
|
grant.setGrantedReason(body.get("grantedReason").getAsString());
|
||||||
|
|
||||||
|
if (body.has("duration")) {
|
||||||
|
grant.setDuration(body.get("duration").getAsLong());
|
||||||
|
grant.setEnd(grant.getDuration() == -1 ? -1
|
||||||
|
: grant.getGrantedAt() + grant.getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.has("removedReason"))
|
||||||
|
grant.setRemovedReason(body.get("removedReason").getAsString());
|
||||||
|
|
||||||
|
boolean didRemove = false;
|
||||||
|
if (body.has("removed") && body.get("removed").getAsBoolean()) {
|
||||||
|
grant.setRemovedAt(body.has("removedAt")
|
||||||
|
? body.get("removedAt").getAsLong()
|
||||||
|
: System.currentTimeMillis());
|
||||||
|
grant.setRemovedBy(body.get("removedBy").getAsString());
|
||||||
|
grant.setRemoved(true);
|
||||||
|
didRemove = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
grantService.saveGrant(grant);
|
||||||
|
|
||||||
|
if (didRemove)
|
||||||
|
api.getRedisService().publish(new GrantRemovePacket(grant.getUuid(), grant.getRank()));
|
||||||
|
|
||||||
|
api.getRedisService().publish(new ProfileUpdatePacket(grant.getUuid()));
|
||||||
|
return new ResponseEntity<>(grant.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/clear")
|
||||||
|
public ResponseEntity<JsonElement> clearGrants(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
AtomicInteger removed = new AtomicInteger();
|
||||||
|
grantService.getGrantsOf(uuid).stream()
|
||||||
|
.filter(grant -> grant.isActive()
|
||||||
|
&& !grant.isRemoved()
|
||||||
|
&& !grant.asRank().isDefaultRank())
|
||||||
|
.forEach(grant -> {
|
||||||
|
grant.setRemoved(true);
|
||||||
|
grant.setRemovedReason(body.get("removedReason").getAsString());
|
||||||
|
grant.setRemovedAt(body.has("removedAt")
|
||||||
|
? body.get("removedAt").getAsLong()
|
||||||
|
: System.currentTimeMillis());
|
||||||
|
grant.setRemovedBy(body.get("removedBy").getAsString());
|
||||||
|
grantService.saveGrant(grant);
|
||||||
|
removed.getAndIncrement();
|
||||||
|
});
|
||||||
|
|
||||||
|
response.add("removed", removed.get());
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getSingleGrant(@PathVariable(name = "id") UUID id,
|
||||||
|
@RequestParam(name = "webResolved", defaultValue = "false") boolean web) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
Optional<Grant> grantOpt = grantService.getGrant(id);
|
||||||
|
if (!grantOpt.isPresent()) {
|
||||||
|
response.add("message", "Grant not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Grant grant = grantOpt.get();
|
||||||
|
JsonObject object = grant.toJson();
|
||||||
|
|
||||||
|
if (web) {
|
||||||
|
Rank rank = grant.asRank();
|
||||||
|
if (rank != null)
|
||||||
|
object.add("resolvedRank", rank.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(object, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package org.hcrival.api.profile.grant;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GrantService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, Grant> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, Grant>() {
|
||||||
|
@Override
|
||||||
|
public Grant load(UUID id) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getGrants()
|
||||||
|
.find(Filters.eq("id", id.toString())).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new Grant(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, List<Grant>> playerCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, List<Grant>>() {
|
||||||
|
@Override
|
||||||
|
public List<Grant> load(UUID uuid) {
|
||||||
|
List<Grant> grants = new ArrayList<>();
|
||||||
|
api.getMongoService().getGrants().find(Filters.eq("uuid", uuid.toString()))
|
||||||
|
.forEach((Block<? super Document>) document -> {
|
||||||
|
Grant grant = new Grant(document);
|
||||||
|
if (grant.asRank() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cache.put(grant.getId(), grant);
|
||||||
|
grants.add(grant);
|
||||||
|
});
|
||||||
|
|
||||||
|
return grants;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<Grant> getGrant(UUID id) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Grant> getGrantsOf(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return playerCache.get(uuid);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveGrant(Grant grant) {
|
||||||
|
api.getMongoService().getGrants().replaceOne(
|
||||||
|
Filters.eq("id", grant.getId().toString()),
|
||||||
|
grant.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(grant.getId(), grant);
|
||||||
|
playerCache.refresh(grant.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package org.hcrival.api.profile.grant;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.rank.Rank;
|
||||||
|
import org.hcrival.api.util.UUIDCache;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping
|
||||||
|
public class StaffController {
|
||||||
|
|
||||||
|
private static final int STAFF_WEIGHT = 160;
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@GetMapping(path = "/staffList")
|
||||||
|
public ResponseEntity<JsonElement> staffList() {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
for (Rank rank : api.getRankService().getRanks()) {
|
||||||
|
if (rank.getWeight() < STAFF_WEIGHT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
JsonObject rankObject = new JsonObject();
|
||||||
|
rankObject.addProperty("uuid", rank.getUuid().toString());
|
||||||
|
rankObject.addProperty("name", rank.getName());
|
||||||
|
rankObject.addProperty("weight", rank.getWeight());
|
||||||
|
rankObject.addProperty("webColor", rank.getWebColor());
|
||||||
|
|
||||||
|
JsonArray members = new JsonArray();
|
||||||
|
|
||||||
|
api.getMongoService().getGrants().find(
|
||||||
|
Filters.and(
|
||||||
|
Filters.eq("rank", rank.getUuid().toString()),
|
||||||
|
Filters.eq("removed", false)
|
||||||
|
)
|
||||||
|
).forEach((Block<? super Document>) document -> {
|
||||||
|
Grant grant = new Grant(document);
|
||||||
|
if (grant.isRemoved() || !grant.isActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
JsonObject member = new JsonObject();
|
||||||
|
member.addProperty("uuid", grant.getUuid().toString());
|
||||||
|
|
||||||
|
String name = UUIDCache.getName(grant.getUuid());
|
||||||
|
member.addProperty("name", name == null ? "N/A" : name);
|
||||||
|
|
||||||
|
members.add(member);
|
||||||
|
});
|
||||||
|
|
||||||
|
rankObject.add("members", members);
|
||||||
|
array.add(rankObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(array, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package org.hcrival.api.profile.grant.rpacket;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.redis.packet.RPacket;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class GrantAddPacket implements RPacket {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private UUID rankUuid;
|
||||||
|
private long duration;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "cc.invictusgames.invictus.grant.packets.GrantAddPacket";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.hcrival.api.profile.grant.rpacket;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.redis.packet.RPacket;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class GrantRemovePacket implements RPacket {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private UUID rankUuid;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "cc.invictusgames.invictus.grant.packets.GrantRemovePacket";
|
||||||
|
}
|
||||||
|
}
|
47
API/src/main/java/org/hcrival/api/profile/note/Note.java
Normal file
47
API/src/main/java/org/hcrival/api/profile/note/Note.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package org.hcrival.api.profile.note;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Note {
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
private UUID uuid;
|
||||||
|
private String addedBy;
|
||||||
|
private String note;
|
||||||
|
private String addedOn;
|
||||||
|
private long addedAt;
|
||||||
|
|
||||||
|
public Note(Document document) {
|
||||||
|
this.id = UUID.fromString(document.getString("id"));
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.addedBy = document.getString("addedBy");
|
||||||
|
this.note = document.getString("note");
|
||||||
|
this.addedOn = document.getString("addedOn");
|
||||||
|
this.addedAt = document.get("addedAt", Number.class).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id.toString());
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("addedBy", addedBy);
|
||||||
|
document.append("note", note);
|
||||||
|
document.append("addedOn", addedOn);
|
||||||
|
document.append("addedAt", addedAt);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
package org.hcrival.api.profile.note;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/profile/{uuid}/notes")
|
||||||
|
@Controller(value = "/profile/{uuid}/notes")
|
||||||
|
public class NoteController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final NoteService noteService;
|
||||||
|
|
||||||
|
public NoteController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.noteService = api.getNoteService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping()
|
||||||
|
public ResponseEntity<JsonElement> addNote(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
UUID id = body.has("id")
|
||||||
|
? UUID.fromString(body.get("id").getAsString())
|
||||||
|
: UUID.randomUUID();
|
||||||
|
|
||||||
|
if (noteService.getNote(id).isPresent()) {
|
||||||
|
response.add("message", "Note already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Note note = new Note();
|
||||||
|
note.setId(id);
|
||||||
|
note.setUuid(uuid);
|
||||||
|
note.setAddedBy(body.get("addedBy").getAsString());
|
||||||
|
note.setNote(body.get("note").getAsString());
|
||||||
|
note.setAddedOn(body.has("addedOn")
|
||||||
|
? body.get("addedOn").getAsString()
|
||||||
|
: "Website");
|
||||||
|
note.setAddedAt(body.has("addedAt")
|
||||||
|
? body.get("addedAt").getAsLong()
|
||||||
|
: System.currentTimeMillis());
|
||||||
|
|
||||||
|
noteService.saveNote(note);
|
||||||
|
return new ResponseEntity<>(note.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> getNotesOf(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonArray notes = new JsonArray();
|
||||||
|
noteService.getNotesOf(uuid).forEach(note -> notes.add(note.toJson()));
|
||||||
|
return new ResponseEntity<>(notes, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> updateNote(@RequestBody JsonObject body,
|
||||||
|
@PathVariable(name = "uuid") UUID uuid,
|
||||||
|
@PathVariable(name = "id") UUID id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Note> noteOpt = noteService.getNote(id);
|
||||||
|
|
||||||
|
if (!noteOpt.isPresent()) {
|
||||||
|
response.add("message", "Note not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Note note = noteOpt.get();
|
||||||
|
|
||||||
|
if (body.has("note"))
|
||||||
|
note.setNote(body.get("note").getAsString());
|
||||||
|
|
||||||
|
noteService.saveNote(note);
|
||||||
|
return new ResponseEntity<>(note.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> removeNote(@PathVariable(name = "uuid") UUID uuid,
|
||||||
|
@PathVariable(name = "id") UUID id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Note> noteOpt = noteService.getNote(id);
|
||||||
|
|
||||||
|
if (!noteOpt.isPresent()) {
|
||||||
|
response.add("message", "Note not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Note note = noteOpt.get();
|
||||||
|
noteService.deleteNote(note);
|
||||||
|
return new ResponseEntity<>(note.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package org.hcrival.api.profile.note;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NoteService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, Note> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, Note>() {
|
||||||
|
@Override
|
||||||
|
public Note load(UUID id) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getNotes()
|
||||||
|
.find(Filters.eq("id", id.toString())).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new Note(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, List<Note>> playerCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, List<Note>>() {
|
||||||
|
@Override
|
||||||
|
public List<Note> load(UUID uuid) {
|
||||||
|
List<Note> notes = new ArrayList<>();
|
||||||
|
api.getMongoService().getNotes().find(Filters.eq("uuid", uuid.toString()))
|
||||||
|
.forEach((Block<? super Document>) document -> {
|
||||||
|
Note note = new Note(document);
|
||||||
|
cache.put(note.getId(), note);
|
||||||
|
notes.add(note);
|
||||||
|
});
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<Note> getNote(UUID id) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Note> getNotesOf(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return playerCache.get(uuid);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveNote(Note note) {
|
||||||
|
api.getMongoService().getNotes().replaceOne(
|
||||||
|
Filters.eq("id", note.getId().toString()),
|
||||||
|
note.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(note.getId(), note);
|
||||||
|
playerCache.refresh(note.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteNote(Note note) {
|
||||||
|
api.getMongoService().getNotes().deleteOne(Filters.eq("id", note.getId().toString()));
|
||||||
|
cache.refresh(note.getId());
|
||||||
|
playerCache.refresh(note.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.hcrival.api.profile.rpacket;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.redis.packet.RPacket;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ProfileUpdatePacket implements RPacket {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "cc.invictusgames.invictus.profile.packets.ProfileUpdatePacket";
|
||||||
|
}
|
||||||
|
}
|
77
API/src/main/java/org/hcrival/api/punishment/Punishment.java
Normal file
77
API/src/main/java/org/hcrival/api/punishment/Punishment.java
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package org.hcrival.api.punishment;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Punishment {
|
||||||
|
|
||||||
|
private UUID id;
|
||||||
|
private UUID uuid;
|
||||||
|
private String punishmentType;
|
||||||
|
private String punishedBy;
|
||||||
|
private long punishedAt;
|
||||||
|
private String punishedReason;
|
||||||
|
private String punishedServerType;
|
||||||
|
private String punishedServer;
|
||||||
|
private String removedBy = "N/A";
|
||||||
|
private long removedAt = -1;
|
||||||
|
private String removedReason = "N/A";
|
||||||
|
private long duration;
|
||||||
|
private long end;
|
||||||
|
private boolean removed;
|
||||||
|
|
||||||
|
public Punishment(Document document) {
|
||||||
|
this.id = UUID.fromString(document.getString("id"));
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.punishmentType = document.getString("punishmentType");
|
||||||
|
this.punishedBy = document.getString("punishedBy");
|
||||||
|
this.punishedAt = document.get("punishedAt", Number.class).longValue();
|
||||||
|
this.punishedReason = document.getString("punishedReason");
|
||||||
|
this.punishedServerType = document.getString("punishedServerType");
|
||||||
|
this.punishedServer = document.getString("punishedServer");
|
||||||
|
this.removedBy = document.getString("removedBy");
|
||||||
|
this.removedAt = document.get("removedAt", Number.class).longValue();
|
||||||
|
this.removedReason = document.getString("removedReason");
|
||||||
|
this.duration = document.get("duration", Number.class).longValue();
|
||||||
|
this.end = document.get("end", Number.class).longValue();
|
||||||
|
this.removed = document.getBoolean("removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("id", id.toString());
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("punishmentType", punishmentType);
|
||||||
|
document.append("punishedBy", punishedBy);
|
||||||
|
document.append("punishedAt", punishedAt);
|
||||||
|
document.append("punishedReason", punishedReason);
|
||||||
|
document.append("punishedServerType", punishedServerType);
|
||||||
|
document.append("punishedServer", punishedServer);
|
||||||
|
document.append("removedBy", removedBy);
|
||||||
|
document.append("removedAt", removedAt);
|
||||||
|
document.append("removedReason", removedReason);
|
||||||
|
document.append("duration", duration);
|
||||||
|
document.append("end", end);
|
||||||
|
document.append("removed", removed);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
if (end == -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return end >= System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,294 @@
|
|||||||
|
package org.hcrival.api.punishment;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.profile.ProfileService;
|
||||||
|
import org.hcrival.api.profile.rpacket.ProfileUpdatePacket;
|
||||||
|
import org.hcrival.api.punishment.rpacket.PunishmentCreatePacket;
|
||||||
|
import org.hcrival.api.punishment.rpacket.PunishmentRemovePacket;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.UUIDCache;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/punishment")
|
||||||
|
public class PunishmentController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final ProfileService profileService;
|
||||||
|
private final PunishmentService punishmentService;
|
||||||
|
|
||||||
|
public PunishmentController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.profileService = api.getProfileService();
|
||||||
|
this.punishmentService = api.getPunishmentService();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> getPunishment(@PathVariable(name = "id") UUID id,
|
||||||
|
@RequestParam(name = "webResolved", defaultValue = "false") boolean web) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Punishment> punishment = punishmentService.getPunishment(id);
|
||||||
|
|
||||||
|
if (!punishment.isPresent()) {
|
||||||
|
response.add("message", "Punishment not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!web)
|
||||||
|
return new ResponseEntity<>(punishment.get().toJson(), HttpStatus.OK);
|
||||||
|
|
||||||
|
JsonObject object = punishment.get().toJson();
|
||||||
|
|
||||||
|
String punishedBy = object.get("punishedBy").getAsString();
|
||||||
|
object.addProperty("resolvedPunishedBy", UUIDCache.isUuid(punishedBy)
|
||||||
|
? UUIDCache.getName(UUID.fromString(punishedBy))
|
||||||
|
: punishedBy);
|
||||||
|
|
||||||
|
if (object.get("removedBy") != null) {
|
||||||
|
String removedBy = object.get("removedBy").getAsString();
|
||||||
|
object.addProperty("resolvedRemovedBy", UUIDCache.isUuid(removedBy)
|
||||||
|
? UUIDCache.getName(UUID.fromString(removedBy))
|
||||||
|
: removedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(object, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/profile/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getPunishmentsOf(@PathVariable(name = "uuid") UUID uuid,
|
||||||
|
@RequestParam(name = "webResolved", defaultValue = "false") boolean web) {
|
||||||
|
JsonArray punishments = new JsonArray();
|
||||||
|
punishmentService.getPunishmentsOf(uuid).forEach(punishment -> punishments.add(punishment.toJson()));
|
||||||
|
|
||||||
|
if (!web)
|
||||||
|
return new ResponseEntity<>(punishments, HttpStatus.OK);
|
||||||
|
|
||||||
|
for (JsonElement element : punishments) {
|
||||||
|
JsonObject punishment = element.getAsJsonObject();
|
||||||
|
|
||||||
|
String punishedBy = punishment.get("punishedBy").getAsString();
|
||||||
|
punishment.addProperty("resolvedPunishedBy", UUIDCache.isUuid(punishedBy)
|
||||||
|
? UUIDCache.getName(UUID.fromString(punishedBy))
|
||||||
|
: punishedBy);
|
||||||
|
|
||||||
|
if (punishment.get("removedBy") != null) {
|
||||||
|
String removedBy = punishment.get("removedBy").getAsString();
|
||||||
|
punishment.addProperty("resolvedRemovedBy", UUIDCache.isUuid(removedBy)
|
||||||
|
? UUIDCache.getName(UUID.fromString(removedBy))
|
||||||
|
: removedBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(punishments, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/appealpunishment/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getAppealPunishment(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Profile> profileOpt = profileService.getProfile(uuid);
|
||||||
|
|
||||||
|
if (!profileOpt.isPresent()) {
|
||||||
|
response.add("message", "Profile not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile profile = profileOpt.get();
|
||||||
|
Optional<Punishment> activePunishment = profile.getActivePunishment("BLACKLIST");
|
||||||
|
if (activePunishment.isPresent()) {
|
||||||
|
response.add("canAppeal", false);
|
||||||
|
response.add("message", "Cannot appeal blacklists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
activePunishment = profile.getActivePunishment("BAN");
|
||||||
|
if (!activePunishment.isPresent())
|
||||||
|
activePunishment = profile.getActivePunishment("MUTE");
|
||||||
|
|
||||||
|
if (!activePunishment.isPresent()) {
|
||||||
|
response.add("canAppeal", false);
|
||||||
|
response.add("message", "No active punishment");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.add("name", profile.getName());
|
||||||
|
response.add("punishment", activePunishment.get().toJson());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> createPunishment(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
|
||||||
|
UUID id = body.has("id")
|
||||||
|
? UUID.fromString(body.get("id").getAsString())
|
||||||
|
: UUID.randomUUID();
|
||||||
|
|
||||||
|
if (punishmentService.getPunishment(id).isPresent()) {
|
||||||
|
response.add("message", "Punishment already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Punishment punishment = new Punishment();
|
||||||
|
punishment.setId(id);
|
||||||
|
punishment.setUuid(uuid);
|
||||||
|
punishment.setPunishmentType(body.get("punishmentType").getAsString());
|
||||||
|
punishment.setPunishedBy(body.get("punishedBy").getAsString());
|
||||||
|
|
||||||
|
punishment.setPunishedAt(body.has("punishedAt")
|
||||||
|
? body.get("punishedAt").getAsLong()
|
||||||
|
: System.currentTimeMillis());
|
||||||
|
|
||||||
|
punishment.setPunishedReason(body.get("punishedReason").getAsString());
|
||||||
|
|
||||||
|
punishment.setPunishedServerType(body.has("punishedServerType")
|
||||||
|
? body.get("punishedServerType").getAsString()
|
||||||
|
: "Website");
|
||||||
|
|
||||||
|
punishment.setPunishedServer(body.has("punishedServer")
|
||||||
|
? body.get("punishedServer").getAsString()
|
||||||
|
: "Website");
|
||||||
|
|
||||||
|
punishment.setRemovedBy("N/A");
|
||||||
|
punishment.setRemovedAt(-1);
|
||||||
|
punishment.setRemovedReason("N/A");
|
||||||
|
punishment.setDuration(body.get("duration").getAsLong());
|
||||||
|
|
||||||
|
punishment.setEnd(body.has("end")
|
||||||
|
? body.get("end").getAsLong()
|
||||||
|
: (punishment.getDuration() == -1
|
||||||
|
? -1 : punishment.getPunishedAt() + punishment.getDuration()));
|
||||||
|
|
||||||
|
punishment.setRemoved(false);
|
||||||
|
|
||||||
|
punishmentService.savePunishment(punishment);
|
||||||
|
|
||||||
|
String executor = "§4§lConsole";
|
||||||
|
if (UUIDCache.isUuid(punishment.getPunishedBy())) {
|
||||||
|
UUID executorId = UUID.fromString(punishment.getPunishedBy());
|
||||||
|
Optional<Profile> profile = api.getProfileService().getProfile(executorId);
|
||||||
|
if (profile.isPresent())
|
||||||
|
executor = profile.get().getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<String> flags = new HashSet<>();
|
||||||
|
if (body.has("flags")) {
|
||||||
|
for (JsonElement array : body.get("flags").getAsJsonArray()) {
|
||||||
|
flags.add(array.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getRedisService().publish(new PunishmentCreatePacket(
|
||||||
|
punishment.getUuid(),
|
||||||
|
punishment.getPunishmentType(),
|
||||||
|
executor,
|
||||||
|
punishment.getPunishedReason(),
|
||||||
|
punishment.getPunishedServerType(),
|
||||||
|
punishment.getPunishedServer(),
|
||||||
|
punishment.getDuration(),
|
||||||
|
flags
|
||||||
|
));
|
||||||
|
|
||||||
|
api.getRedisService().publish(new ProfileUpdatePacket(punishment.getUuid()));
|
||||||
|
|
||||||
|
return new ResponseEntity<>(punishment.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/{id}")
|
||||||
|
public ResponseEntity<JsonElement> updatePunishment(@RequestBody JsonObject body, @PathVariable(name = "id") UUID id) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Punishment> punishmentOpt = punishmentService.getPunishment(id);
|
||||||
|
|
||||||
|
if (!punishmentOpt.isPresent()) {
|
||||||
|
response.add("message", "Punishment not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Punishment punishment = punishmentOpt.get();
|
||||||
|
|
||||||
|
if (body.has("punishedReason"))
|
||||||
|
punishment.setPunishedReason(body.get("punishedReason").getAsString());
|
||||||
|
|
||||||
|
if (body.has("duration")) {
|
||||||
|
punishment.setDuration(body.get("duration").getAsLong());
|
||||||
|
punishment.setEnd(punishment.getDuration() == -1 ? -1
|
||||||
|
: punishment.getPunishedAt() + punishment.getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.has("removedReason"))
|
||||||
|
punishment.setRemovedReason(body.get("removedReason").getAsString());
|
||||||
|
|
||||||
|
if (body.has("removedBy"))
|
||||||
|
punishment.setRemovedBy(body.get("removedBy").getAsString());
|
||||||
|
|
||||||
|
boolean didRemove = false;
|
||||||
|
if (body.has("removed") && body.get("removed").getAsBoolean()) {
|
||||||
|
punishment.setRemovedAt(body.has("removedAt")
|
||||||
|
? body.get("removedAt").getAsLong()
|
||||||
|
: System.currentTimeMillis());
|
||||||
|
punishment.setRemoved(true);
|
||||||
|
didRemove = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
punishmentService.savePunishment(punishment);
|
||||||
|
|
||||||
|
if (didRemove
|
||||||
|
&& !punishment.getPunishmentType().equals("KICK")
|
||||||
|
&& !punishment.getPunishmentType().equals("WARN")) {
|
||||||
|
String executor = "§4§lConsole";
|
||||||
|
if (UUIDCache.isUuid(punishment.getRemovedBy())) {
|
||||||
|
UUID executorId = UUID.fromString(punishment.getRemovedBy());
|
||||||
|
Optional<Profile> profile = api.getProfileService().getProfile(executorId);
|
||||||
|
if (profile.isPresent())
|
||||||
|
executor = profile.get().getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getRedisService().publish(new PunishmentRemovePacket(
|
||||||
|
punishment.getUuid(),
|
||||||
|
executor,
|
||||||
|
punishment.getPunishmentType(),
|
||||||
|
punishment.getRemovedReason(),
|
||||||
|
body.has("silent") && body.get("silent").getAsBoolean()
|
||||||
|
));
|
||||||
|
|
||||||
|
api.getRedisService().publish(new ProfileUpdatePacket(punishment.getUuid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(punishment.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/clear")
|
||||||
|
public ResponseEntity<JsonElement> clearPunishments(@RequestBody JsonObject body) {
|
||||||
|
int cleared = punishmentService.clearPunishments(
|
||||||
|
body.get("removedBy").getAsString(),
|
||||||
|
body.get("removedReason").getAsString(),
|
||||||
|
body.has("removedAt") ? body.get("removedAt").getAsLong() : System.currentTimeMillis(),
|
||||||
|
Arrays.asList(body.get("types").getAsString().split(","))
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(new JsonBuilder().add("cleared", cleared).build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping(path = "/staffrollback")
|
||||||
|
public ResponseEntity<JsonElement> staffRollback(@RequestBody JsonObject body) {
|
||||||
|
int cleared = punishmentService.staffRollback(
|
||||||
|
body.get("punishedBy").getAsString(),
|
||||||
|
body.has("removedReason") ? body.get("removedReason").getAsString() : "Staff Rollback",
|
||||||
|
body.get("removedBy").getAsString(),
|
||||||
|
body.has("removedAt") ? body.get("removedAt").getAsLong() : System.currentTimeMillis(),
|
||||||
|
body.get("maxTime").getAsLong()
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ResponseEntity<>(new JsonBuilder().add("cleared", cleared).build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package org.hcrival.api.punishment;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PunishmentService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, Punishment> cache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, Punishment>() {
|
||||||
|
@Override
|
||||||
|
public Punishment load(UUID id) throws DataNotFoundException {
|
||||||
|
Document document = api.getMongoService().getPunishments()
|
||||||
|
.find(Filters.eq("id", id.toString())).first();
|
||||||
|
if (document == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
return new Punishment(document);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final LoadingCache<UUID, List<Punishment>> playerCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<UUID, List<Punishment>>() {
|
||||||
|
@Override
|
||||||
|
public List<Punishment> load(UUID uuid) {
|
||||||
|
List<Punishment> punishments = new ArrayList<>();
|
||||||
|
api.getMongoService().getPunishments().find(Filters.eq("uuid", uuid.toString()))
|
||||||
|
.forEach((Block<? super Document>) document -> {
|
||||||
|
Punishment punishment = new Punishment(document);
|
||||||
|
cache.put(punishment.getId(), punishment);
|
||||||
|
punishments.add(punishment);
|
||||||
|
});
|
||||||
|
|
||||||
|
return punishments;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Optional<Punishment> getPunishment(UUID id) {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(cache.get(id));
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Punishment> getPunishmentsOf(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return playerCache.get(uuid);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void savePunishment(Punishment punishment) {
|
||||||
|
api.getMongoService().getPunishments().replaceOne(
|
||||||
|
Filters.eq("id", punishment.getId().toString()),
|
||||||
|
punishment.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(punishment.getId(), punishment);
|
||||||
|
playerCache.refresh(punishment.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int clearPunishments(String removedBy, String removedReason, long removedAt, List<String> types) {
|
||||||
|
int cleared = 0;
|
||||||
|
|
||||||
|
for (Document document : api.getMongoService().getPunishments().find()) {
|
||||||
|
Punishment punishment = new Punishment(document);
|
||||||
|
|
||||||
|
if (!punishment.isActive() || punishment.isRemoved())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (punishment.getPunishmentType().equals("BLACKLIST")
|
||||||
|
&& punishment.getPunishedReason().equalsIgnoreCase("Chargeback"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!types.contains(punishment.getPunishmentType()) && !types.contains("ALL"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
punishment.setRemoved(true);
|
||||||
|
punishment.setRemovedBy(removedBy);
|
||||||
|
punishment.setRemovedReason(removedReason);
|
||||||
|
punishment.setRemovedAt(removedAt);
|
||||||
|
|
||||||
|
api.getMongoService().getPunishments().replaceOne(
|
||||||
|
Filters.eq("id", punishment.getId().toString()),
|
||||||
|
punishment.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cleared++;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.invalidateAll();
|
||||||
|
playerCache.invalidateAll();
|
||||||
|
|
||||||
|
return cleared;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int staffRollback(String punishedBy, String removedReason, String removedBy, long removedAt, long maxTime) {
|
||||||
|
int cleared = 0;
|
||||||
|
List<UUID> playersToRefresh = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Document document : api.getMongoService().getPunishments().find(Filters.and(
|
||||||
|
Filters.eq("punishedBy", punishedBy),
|
||||||
|
Filters.gt("punishedAt", maxTime)))) {
|
||||||
|
Punishment punishment = new Punishment(document);
|
||||||
|
if (!punishment.isActive() || punishment.isRemoved())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
punishment.setRemoved(true);
|
||||||
|
punishment.setRemovedBy(removedBy);
|
||||||
|
punishment.setRemovedReason(removedReason);
|
||||||
|
punishment.setRemovedAt(removedAt);
|
||||||
|
|
||||||
|
api.getMongoService().getPunishments().replaceOne(
|
||||||
|
Filters.eq("id", punishment.getId().toString()),
|
||||||
|
punishment.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(punishment.getId(), punishment);
|
||||||
|
if (!playersToRefresh.contains(punishment.getUuid()))
|
||||||
|
playersToRefresh.add(punishment.getUuid());
|
||||||
|
|
||||||
|
cleared++;
|
||||||
|
}
|
||||||
|
|
||||||
|
playersToRefresh.forEach(playerCache::refresh);
|
||||||
|
|
||||||
|
return cleared;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.hcrival.api.punishment.rpacket;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.redis.packet.RPacket;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PunishmentCreatePacket implements RPacket {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String type;
|
||||||
|
private String executor;
|
||||||
|
private String reason;
|
||||||
|
private String serverType;
|
||||||
|
private String server;
|
||||||
|
private long duration;
|
||||||
|
private Set<String> flags;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "cc.invictusgames.invictus.punishment.packets.PunishmentCreatePacket";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.hcrival.api.punishment.rpacket;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hcrival.api.redis.packet.RPacket;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PunishmentRemovePacket implements RPacket {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String executor;
|
||||||
|
private String type;
|
||||||
|
private String reason;
|
||||||
|
private boolean silent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "cc.invictusgames.invictus.punishment.packets.PunishmentRemovePacket";
|
||||||
|
}
|
||||||
|
}
|
147
API/src/main/java/org/hcrival/api/rank/Rank.java
Normal file
147
API/src/main/java/org/hcrival/api/rank/Rank.java
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package org.hcrival.api.rank;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Rank {
|
||||||
|
|
||||||
|
private UUID uuid;
|
||||||
|
private String name;
|
||||||
|
private String prefix = "§f";
|
||||||
|
private String suffix = "§f";
|
||||||
|
private String color = "§f";
|
||||||
|
private String chatColor = "§f";
|
||||||
|
private int weight = 0;
|
||||||
|
private int queuePriority = 0;
|
||||||
|
private boolean defaultRank = false;
|
||||||
|
private boolean disguisable = false;
|
||||||
|
private List<String> permissions = new ArrayList<>();
|
||||||
|
private String discordId = null;
|
||||||
|
private String staffDiscordId = null;
|
||||||
|
private List<UUID> inherits = new ArrayList<>();
|
||||||
|
|
||||||
|
public Rank(Document document) {
|
||||||
|
this.uuid = UUID.fromString(document.getString("uuid"));
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.prefix = document.getString("prefix");
|
||||||
|
this.suffix = document.getString("suffix");
|
||||||
|
this.color = document.getString("color");
|
||||||
|
this.chatColor = document.getString("chatColor");
|
||||||
|
this.weight = document.getInteger("weight");
|
||||||
|
if (document.containsKey("queuePriority"))
|
||||||
|
this.queuePriority = document.getInteger("queuePriority");
|
||||||
|
else queuePriority = weight;
|
||||||
|
this.defaultRank = document.getBoolean("defaultRank");
|
||||||
|
this.disguisable = document.getBoolean("disguisable");
|
||||||
|
this.permissions = document.getList("permissions", String.class);
|
||||||
|
this.discordId = document.getString("discordId");
|
||||||
|
this.staffDiscordId = document.getString("staffDiscordId");
|
||||||
|
this.inherits = document.getList("inherits", String.class).stream()
|
||||||
|
.map(UUID::fromString)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("uuid", uuid.toString());
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("prefix", prefix);
|
||||||
|
document.append("suffix", suffix);
|
||||||
|
document.append("color", color);
|
||||||
|
document.append("chatColor", chatColor);
|
||||||
|
document.append("weight", weight);
|
||||||
|
document.append("queuePriority", queuePriority);
|
||||||
|
document.append("defaultRank", defaultRank);
|
||||||
|
document.append("disguisable", disguisable);
|
||||||
|
document.append("permissions", permissions);
|
||||||
|
document.append("discordId", discordId);
|
||||||
|
document.append("staffDiscordId", staffDiscordId);
|
||||||
|
document.append("inherits", inherits.stream()
|
||||||
|
.map(UUID::toString)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getInheritedPermissions() {
|
||||||
|
List<String> inheritedPermissions = new ArrayList<>();
|
||||||
|
for (UUID inherit : inherits) {
|
||||||
|
Optional<Rank> rankOpt = InvictusAPI.getInstance().getRankService().getRank(inherit);
|
||||||
|
if (rankOpt.isPresent())
|
||||||
|
inheritedPermissions.addAll(rankOpt.get().getAllPermissions());
|
||||||
|
}
|
||||||
|
return inheritedPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllPermissions() {
|
||||||
|
List<String> allPermissions = new ArrayList<>();
|
||||||
|
allPermissions.addAll(getInheritedPermissions());
|
||||||
|
allPermissions.addAll(permissions);
|
||||||
|
return allPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWebColor() {
|
||||||
|
String input = this.color.replaceAll("§", "")
|
||||||
|
.replaceAll("l", "")
|
||||||
|
.replaceAll("o", "")
|
||||||
|
.replaceAll("n", "")
|
||||||
|
.replaceAll("m", "")
|
||||||
|
.replaceAll("k", "");
|
||||||
|
|
||||||
|
switch (input) {
|
||||||
|
case "0":
|
||||||
|
return "#000000";
|
||||||
|
case "1":
|
||||||
|
return "#0000AA";
|
||||||
|
case "2":
|
||||||
|
return "#00AA00";
|
||||||
|
case "3":
|
||||||
|
return "#00AAAA";
|
||||||
|
case "4":
|
||||||
|
return "#AA0000";
|
||||||
|
case "5":
|
||||||
|
return "#AA00AA";
|
||||||
|
case "6":
|
||||||
|
return "#FFAA00";
|
||||||
|
/*case "7":
|
||||||
|
return "#AAAAAA";*/
|
||||||
|
case "8":
|
||||||
|
return "#555555";
|
||||||
|
case "9":
|
||||||
|
return "#5555FF";
|
||||||
|
case "a":
|
||||||
|
return "#55FF55";
|
||||||
|
case "b":
|
||||||
|
return "#55FFFF";
|
||||||
|
case "c":
|
||||||
|
return "#FF5555";
|
||||||
|
case "d":
|
||||||
|
return "#FF55FF";
|
||||||
|
case "e":
|
||||||
|
return "#FFFF55";
|
||||||
|
case "7":
|
||||||
|
case "f":
|
||||||
|
default:
|
||||||
|
//return "#FFFFFF";
|
||||||
|
return "#AAAAAA";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
94
API/src/main/java/org/hcrival/api/rank/RankController.java
Normal file
94
API/src/main/java/org/hcrival/api/rank/RankController.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package org.hcrival.api.rank;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/rank")
|
||||||
|
public class RankController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final RankService rankService;
|
||||||
|
|
||||||
|
public RankController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.rankService = api.getRankService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> getRanks(@RequestParam(name = "webResolved", defaultValue = "false") boolean web) {
|
||||||
|
JsonArray ranks = new JsonArray();
|
||||||
|
rankService.getRanks().forEach(rank -> {
|
||||||
|
JsonObject object = rank.toJson();
|
||||||
|
|
||||||
|
if (web)
|
||||||
|
object.addProperty("webColor", rank.getWebColor());
|
||||||
|
|
||||||
|
ranks.add(object);
|
||||||
|
});
|
||||||
|
return new ResponseEntity<>(ranks, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getRank(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Optional<Rank> rank = rankService.getRank(uuid);
|
||||||
|
|
||||||
|
if (!rank.isPresent()) {
|
||||||
|
response.add("message", "Rank not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResponseEntity<>(rank.get().toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createRank(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Rank rank = JsonConfigurationService.gson.fromJson(body, Rank.class);
|
||||||
|
if (rankService.getRank(rank.getUuid()).isPresent()) {
|
||||||
|
response.add("message", "Rank already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
rankService.saveRank(rank);
|
||||||
|
return new ResponseEntity<>(rank.toJson(), HttpStatus.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping()
|
||||||
|
public ResponseEntity<JsonElement> updateRank(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Rank rank = JsonConfigurationService.gson.fromJson(body, Rank.class);
|
||||||
|
if (!rankService.getRank(rank.getUuid()).isPresent()) {
|
||||||
|
response.add("message", "Rank not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
rankService.saveRank(rank);
|
||||||
|
return new ResponseEntity<>(rank.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> deleteRank(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (!rankService.getRank(uuid).isPresent()) {
|
||||||
|
response.add("message", "Rank not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rank rank = rankService.deleteRank(uuid);
|
||||||
|
return new ResponseEntity<>(rank.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
75
API/src/main/java/org/hcrival/api/rank/RankService.java
Normal file
75
API/src/main/java/org/hcrival/api/rank/RankService.java
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package org.hcrival.api.rank;
|
||||||
|
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RankService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Map<UUID, Rank> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loadRanks() {
|
||||||
|
api.getMongoService().getRanks().find().forEach((Block<? super Document>) document -> {
|
||||||
|
Rank rank = new Rank(document);
|
||||||
|
cache.put(rank.getUuid(), rank);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Rank> getRanks() {
|
||||||
|
return new ArrayList<>(cache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Rank> getRank(UUID uuid) {
|
||||||
|
return Optional.ofNullable(cache.get(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rank getDefaultRank() {
|
||||||
|
Rank found = cache.values().stream()
|
||||||
|
.filter(Rank::isDefaultRank)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (found == null) {
|
||||||
|
found = new Rank();
|
||||||
|
found.setUuid(UUID.randomUUID());
|
||||||
|
found.setName("Member");
|
||||||
|
found.setDefaultRank(true);
|
||||||
|
saveRank(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveRank(Rank rank) {
|
||||||
|
api.getMongoService().getRanks().replaceOne(
|
||||||
|
Filters.eq("uuid", rank.getUuid().toString()),
|
||||||
|
rank.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.put(rank.getUuid(), rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rank deleteRank(UUID uuid) {
|
||||||
|
api.getMongoService().getRanks().deleteOne(Filters.eq("uuid", uuid.toString()));
|
||||||
|
return cache.remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Rank> getByDiscordId(String discordId) {
|
||||||
|
return getRanks().stream()
|
||||||
|
.filter(rank -> rank.getDiscordId() != null && rank.getDiscordId().equals(discordId))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
API/src/main/java/org/hcrival/api/redis/RedisCommand.java
Normal file
20
API/src/main/java/org/hcrival/api/redis/RedisCommand.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package org.hcrival.api.redis;
|
||||||
|
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility interface to simplify redis usage.
|
||||||
|
*
|
||||||
|
* @param <T> the generic type that is return when the command is executed
|
||||||
|
*/
|
||||||
|
public interface RedisCommand<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a redis connection is made and the command is being executed.
|
||||||
|
*
|
||||||
|
* @param redis the redis connection that got opened
|
||||||
|
* @return the result of the command
|
||||||
|
*/
|
||||||
|
T execute(Jedis redis);
|
||||||
|
|
||||||
|
}
|
68
API/src/main/java/org/hcrival/api/redis/RedisService.java
Normal file
68
API/src/main/java/org/hcrival/api/redis/RedisService.java
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package org.hcrival.api.redis;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.redis.packet.RPacket;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
import org.hcrival.api.util.configuration.defaults.RedisConfig;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
|
||||||
|
public class RedisService {
|
||||||
|
|
||||||
|
private final RedisConfig config;
|
||||||
|
|
||||||
|
private final JedisPool pool;
|
||||||
|
|
||||||
|
private final String channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new redis service.
|
||||||
|
*
|
||||||
|
* @param config the {@link NetworkConfiguration} object for this
|
||||||
|
* service to use to create a connection
|
||||||
|
*/
|
||||||
|
public RedisService(RedisConfig config, String channel) {
|
||||||
|
this.config = config;
|
||||||
|
this.channel = channel;
|
||||||
|
this.pool = new JedisPool(config.getHost(), config.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a {@link RedisCommand}.
|
||||||
|
*
|
||||||
|
* @param command the command to execute
|
||||||
|
* @param <T> the generic type the command should return
|
||||||
|
* @return the result of the command
|
||||||
|
*/
|
||||||
|
public <T> T executeCommand(RedisCommand<T> command) {
|
||||||
|
Jedis jedis = null;
|
||||||
|
try {
|
||||||
|
jedis = pool.getResource();
|
||||||
|
if (config.isAuthEnabled())
|
||||||
|
jedis.auth(config.getAuthPassword());
|
||||||
|
|
||||||
|
jedis.select(config.getDbId());
|
||||||
|
return command.execute(jedis);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (jedis != null) {
|
||||||
|
jedis.close();
|
||||||
|
jedis = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void publish(RPacket packet) {
|
||||||
|
this.executeCommand(redis -> {
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
jsonObject.addProperty("packet", packet.getId());
|
||||||
|
jsonObject.addProperty("data", JsonConfigurationService.gson.toJson(packet));
|
||||||
|
redis.publish(this.channel, jsonObject.toString());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
API/src/main/java/org/hcrival/api/redis/packet/RPacket.java
Normal file
14
API/src/main/java/org/hcrival/api/redis/packet/RPacket.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package org.hcrival.api.redis.packet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 31.12.2019 / 03:29
|
||||||
|
* iLib / cc.invictusgames.ilib.redis
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface RPacket {
|
||||||
|
|
||||||
|
void receive();
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
}
|
33
API/src/main/java/org/hcrival/api/tag/Tag.java
Normal file
33
API/src/main/java/org/hcrival/api/tag/Tag.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package org.hcrival.api.tag;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Tag {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
|
public Tag(Document document) {
|
||||||
|
this.name = document.getString("name");
|
||||||
|
this.displayName = document.getString("displayName");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document toBson() {
|
||||||
|
Document document = new Document();
|
||||||
|
document.append("name", name);
|
||||||
|
document.append("displayName", displayName);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject toJson() {
|
||||||
|
return JsonConfigurationService.gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
58
API/src/main/java/org/hcrival/api/tag/TagController.java
Normal file
58
API/src/main/java/org/hcrival/api/tag/TagController.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package org.hcrival.api.tag;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.hcrival.api.util.configuration.JsonConfigurationService;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/tag")
|
||||||
|
public class TagController {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final TagService tagService;
|
||||||
|
|
||||||
|
public TagController(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.tagService = api.getTagService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<JsonElement> getTags() {
|
||||||
|
JsonArray tags = new JsonArray();
|
||||||
|
tagService.getTags().forEach(tag -> tags.add(tag.toJson()));
|
||||||
|
return new ResponseEntity<>(tags, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<JsonElement> createTag(@RequestBody JsonObject body) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
Tag tag = JsonConfigurationService.gson.fromJson(body, Tag.class);
|
||||||
|
if (tagService.getTag(tag.getName()).isPresent()) {
|
||||||
|
response.add("message", "Tag already exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
tagService.saveTag(tag);
|
||||||
|
return new ResponseEntity<>(tag.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{name}")
|
||||||
|
public ResponseEntity<JsonElement> deleteTag(@PathVariable(name = "name") String name) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (!tagService.getTag(name).isPresent()) {
|
||||||
|
response.add("message", "Tag not found");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag tag = tagService.deleteTag(name);
|
||||||
|
return new ResponseEntity<>(tag.toJson(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
API/src/main/java/org/hcrival/api/tag/TagService.java
Normal file
51
API/src/main/java/org/hcrival/api/tag/TagService.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package org.hcrival.api.tag;
|
||||||
|
|
||||||
|
import com.mongodb.Block;
|
||||||
|
import com.mongodb.client.model.Filters;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.mongo.MongoService;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TagService {
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Map<String, Tag> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void loadTags() {
|
||||||
|
api.getMongoService().getTags().find().forEach((Block<? super Document>) document -> {
|
||||||
|
Tag tag = new Tag(document);
|
||||||
|
cache.put(tag.getName(), tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tag> getTags() {
|
||||||
|
return new ArrayList<>(cache.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Tag> getTag(String name) {
|
||||||
|
return Optional.ofNullable(cache.get(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveTag(Tag tag) {
|
||||||
|
api.getMongoService().getTags().replaceOne(
|
||||||
|
Filters.eq("name", tag.getName()),
|
||||||
|
tag.toBson(),
|
||||||
|
MongoService.REPLACE_OPTIONS
|
||||||
|
);
|
||||||
|
cache.put(tag.getName(), tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tag deleteTag(String name) {
|
||||||
|
api.getMongoService().getTags().deleteOne(Filters.eq("name", name));
|
||||||
|
return cache.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
API/src/main/java/org/hcrival/api/totp/TotpController.java
Normal file
67
API/src/main/java/org/hcrival/api/totp/TotpController.java
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package org.hcrival.api.totp;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.warrenstrange.googleauth.GoogleAuthenticator;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/totp")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TotpController {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@GetMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> getData(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
return new ResponseEntity<>(master.getTotpService().getData(uuid), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{uuid}/tryEnable")
|
||||||
|
public ResponseEntity<JsonElement> tryEnable(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (!master.getTotpService().hasCredentials(uuid)) {
|
||||||
|
response.add("message", "Credentials don't exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleAuthenticator authenticator = master.getTotpService().getGoogleAuthenticator();
|
||||||
|
boolean success = authenticator.authorizeUser(uuid.toString(), body.get("code").getAsInt());
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
master.getTotpService().confirmEnabled(uuid);
|
||||||
|
|
||||||
|
response.add("success", success);
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/{uuid}/tryAuth")
|
||||||
|
public ResponseEntity<JsonElement> tryAuth(@RequestBody JsonObject body, @PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
if (!master.getTotpService().hasCredentials(uuid)) {
|
||||||
|
response.add("message", "Credentials don't exists");
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleAuthenticator authenticator = master.getTotpService().getGoogleAuthenticator();
|
||||||
|
response.add("success", authenticator.authorizeUser(uuid.toString(), body.get("code").getAsInt()));
|
||||||
|
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping(path = "/{uuid}")
|
||||||
|
public ResponseEntity<JsonElement> deleteData(@PathVariable(name = "uuid") UUID uuid) {
|
||||||
|
JsonBuilder response = new JsonBuilder();
|
||||||
|
response.add("fieldsDeleted", master.getTotpService().deleteData(uuid));
|
||||||
|
return new ResponseEntity<>(response.build(), HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
82
API/src/main/java/org/hcrival/api/totp/TotpService.java
Normal file
82
API/src/main/java/org/hcrival/api/totp/TotpService.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package org.hcrival.api.totp;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.warrenstrange.googleauth.GoogleAuthenticator;
|
||||||
|
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
|
||||||
|
import com.warrenstrange.googleauth.ICredentialRepository;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.totp.repository.RedisCredentialRepository;
|
||||||
|
import org.hcrival.api.util.JsonBuilder;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class TotpService {
|
||||||
|
|
||||||
|
public static final String SECRET_KEY_FORMAT = "totp:%s:secretKey";
|
||||||
|
public static final String VALIDATION_CODE_FORMAT = "totp:%s:validationCode";
|
||||||
|
public static final String SCRATCH_CODES_FORMAT = "totp:%s:scratchCodes";
|
||||||
|
public static final String CONFIRMED_ENABLED_FORMAT = "totp:%s:confirmedEnabled";
|
||||||
|
|
||||||
|
private final InvictusAPI api;
|
||||||
|
private final GoogleAuthenticator googleAuthenticator;
|
||||||
|
private final ICredentialRepository credentialRepository;
|
||||||
|
|
||||||
|
public TotpService(InvictusAPI api) {
|
||||||
|
this.api = api;
|
||||||
|
this.googleAuthenticator = new GoogleAuthenticator();
|
||||||
|
this.credentialRepository = new RedisCredentialRepository(api);
|
||||||
|
googleAuthenticator.setCredentialRepository(credentialRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject getData(UUID uuid) {
|
||||||
|
return api.getRedisService().executeCommand(redis -> {
|
||||||
|
JsonBuilder builder = new JsonBuilder();
|
||||||
|
|
||||||
|
String format = String.format(SECRET_KEY_FORMAT, uuid.toString());
|
||||||
|
if (redis.exists(format))
|
||||||
|
builder.add("secretKey", redis.get(format));
|
||||||
|
else {
|
||||||
|
GoogleAuthenticatorKey credentials = googleAuthenticator.createCredentials(uuid.toString());
|
||||||
|
builder.add("secretKey", credentials.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
format = String.format(VALIDATION_CODE_FORMAT, uuid.toString());
|
||||||
|
if (redis.exists(format))
|
||||||
|
builder.add("validationCode", redis.get(format));
|
||||||
|
|
||||||
|
format = String.format(SCRATCH_CODES_FORMAT, uuid.toString());
|
||||||
|
if (redis.exists(format))
|
||||||
|
builder.add("scratchCodes", redis.get(format));
|
||||||
|
|
||||||
|
format = String.format(CONFIRMED_ENABLED_FORMAT, uuid.toString());
|
||||||
|
if (redis.exists(format))
|
||||||
|
builder.add("confirmedEnabled", Boolean.valueOf(redis.get(format)));
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void confirmEnabled(UUID uuid) {
|
||||||
|
api.getRedisService().executeCommand(redis ->
|
||||||
|
redis.set(String.format(CONFIRMED_ENABLED_FORMAT, uuid.toString()), String.valueOf(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCredentials(UUID uuid) {
|
||||||
|
return api.getRedisService().executeCommand(redis ->
|
||||||
|
redis.exists(String.format(SECRET_KEY_FORMAT, uuid.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long deleteData(UUID uuid) {
|
||||||
|
return api.getRedisService().executeCommand(redis -> {
|
||||||
|
return redis.del(
|
||||||
|
String.format(SECRET_KEY_FORMAT, uuid.toString()),
|
||||||
|
String.format(VALIDATION_CODE_FORMAT, uuid.toString()),
|
||||||
|
String.format(SCRATCH_CODES_FORMAT, uuid.toString()),
|
||||||
|
String.format(CONFIRMED_ENABLED_FORMAT, uuid.toString())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.hcrival.api.totp.repository;
|
||||||
|
|
||||||
|
import com.warrenstrange.googleauth.ICredentialRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.totp.TotpService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RedisCredentialRepository implements ICredentialRepository {
|
||||||
|
|
||||||
|
private final InvictusAPI master;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSecretKey(String userName) {
|
||||||
|
return master.getRedisService().executeCommand(redis ->
|
||||||
|
redis.get(String.format(TotpService.SECRET_KEY_FORMAT, userName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
|
||||||
|
master.getRedisService().executeCommand(redis -> {
|
||||||
|
redis.set(String.format(TotpService.SECRET_KEY_FORMAT, userName), secretKey);
|
||||||
|
redis.set(String.format(TotpService.VALIDATION_CODE_FORMAT, userName), String.valueOf(validationCode));
|
||||||
|
redis.set(String.format(TotpService.SCRATCH_CODES_FORMAT, userName), StringUtils.join(scratchCodes, ","));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
7
API/src/main/java/org/hcrival/api/util/ChatColor.java
Normal file
7
API/src/main/java/org/hcrival/api/util/ChatColor.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package org.hcrival.api.util;
|
||||||
|
|
||||||
|
public class ChatColor {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
55
API/src/main/java/org/hcrival/api/util/JsonBuilder.java
Normal file
55
API/src/main/java/org/hcrival/api/util/JsonBuilder.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package org.hcrival.api.util;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author langgezockt (langgezockt@gmail.com)
|
||||||
|
* 02.03.2021 / 01:23
|
||||||
|
* InvictusRestAPI / cc.invictusgames.invictusresapi.util
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class JsonBuilder {
|
||||||
|
|
||||||
|
private final JsonObject jsonObject = new JsonObject();
|
||||||
|
|
||||||
|
public JsonBuilder add(String property, String value) {
|
||||||
|
this.jsonObject.addProperty(property, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonBuilder add(String property, Number value) {
|
||||||
|
this.jsonObject.addProperty(property, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonBuilder add(String property, Boolean value) {
|
||||||
|
this.jsonObject.addProperty(property, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonBuilder add(String property, Character value) {
|
||||||
|
this.jsonObject.addProperty(property, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonBuilder add(String property, UUID data) {
|
||||||
|
this.jsonObject.addProperty(property, data.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonBuilder add(String property, JsonElement value) {
|
||||||
|
this.jsonObject.add(property, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject build() {
|
||||||
|
return this.jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildString() {
|
||||||
|
return this.jsonObject.toString();
|
||||||
|
}
|
||||||
|
}
|
50
API/src/main/java/org/hcrival/api/util/PermissionUtil.java
Normal file
50
API/src/main/java/org/hcrival/api/util/PermissionUtil.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package org.hcrival.api.util;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.discord.DiscordData;
|
||||||
|
import org.hcrival.api.profile.Profile;
|
||||||
|
import org.hcrival.api.profile.grant.Grant;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class PermissionUtil {
|
||||||
|
|
||||||
|
public static JsonObject getEffectivePermissions(Profile profile) {
|
||||||
|
JsonObject effectivePermissions = new JsonObject();
|
||||||
|
|
||||||
|
List<Grant> grants = new ArrayList<>(profile.getActiveGrants());
|
||||||
|
grants.sort(Grant.COMPARATOR.reversed());
|
||||||
|
for (Grant grant : grants) {
|
||||||
|
JsonObject rankPerms = convert(grant.asRank().getAllPermissions());
|
||||||
|
for (String key : rankPerms.keySet())
|
||||||
|
effectivePermissions.addProperty(key, rankPerms.get(key).getAsString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
grants.clear();
|
||||||
|
|
||||||
|
JsonObject profilePerms = convert(profile.getPermissions());
|
||||||
|
for (String key : profilePerms.keySet())
|
||||||
|
effectivePermissions.addProperty(key, profilePerms.get(key).getAsString());
|
||||||
|
|
||||||
|
Optional<DiscordData> discordData = InvictusAPI.getInstance().getDiscordService().getByUuid(profile.getUuid());
|
||||||
|
if (discordData.isPresent() && discordData.get().isBoosted()) {
|
||||||
|
effectivePermissions.addProperty("invictus.nitroboost", true);
|
||||||
|
effectivePermissions.addProperty("aresenic.gkit.nitro", true);
|
||||||
|
}
|
||||||
|
return effectivePermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonObject convert(List<String> list) {
|
||||||
|
JsonObject permissions = new JsonObject();
|
||||||
|
list.forEach(permission -> {
|
||||||
|
if (permission.startsWith("-"))
|
||||||
|
permissions.addProperty(permission.substring(1), false);
|
||||||
|
else permissions.addProperty(permission, true);
|
||||||
|
});
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
29
API/src/main/java/org/hcrival/api/util/Statics.java
Normal file
29
API/src/main/java/org/hcrival/api/util/Statics.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package org.hcrival.api.util;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 15.01.2020 / 21:29
|
||||||
|
* iLib / cc.invictusgames.ilib.utils
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Statics {
|
||||||
|
|
||||||
|
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("America/New_York");
|
||||||
|
public static final JsonParser JSON_PARSER = new JsonParser();
|
||||||
|
public static final Gson GSON = new GsonBuilder()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.disableHtmlEscaping()
|
||||||
|
.create();
|
||||||
|
public static final Gson PLAIN_GSON = new GsonBuilder().create();
|
||||||
|
public static Joiner SPACE_JOINER = Joiner.on(" ");
|
||||||
|
public static Joiner COMMA_JOINER = Joiner.on(", ");
|
||||||
|
|
||||||
|
}
|
233
API/src/main/java/org/hcrival/api/util/TimeUtils.java
Normal file
233
API/src/main/java/org/hcrival/api/util/TimeUtils.java
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
package org.hcrival.api.util;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author langgezockt (langgezockt@gmail.com)
|
||||||
|
* 11.06.2019 / 09:55
|
||||||
|
* iUtils / cc.invictusgames.iutils.utils
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TimeUtils {
|
||||||
|
|
||||||
|
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("America/New_York");
|
||||||
|
public static final long MINUTE = TimeUnit.MINUTES.toSeconds(1);
|
||||||
|
private static final ThreadLocal<StringBuilder> mmssBuilder = ThreadLocal.withInitial(StringBuilder::new);
|
||||||
|
|
||||||
|
public static long parseTime(String input) {
|
||||||
|
if (input.equals("0") || input.equals("") || input.equalsIgnoreCase("0s")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] lifeMatch = { "w", "d", "h", "m", "s" };
|
||||||
|
long[] lifeInterval = {
|
||||||
|
TimeUnit.DAYS.toMillis(7), //w
|
||||||
|
TimeUnit.DAYS.toMillis(1), //d
|
||||||
|
TimeUnit.HOURS.toMillis(1), //h
|
||||||
|
TimeUnit.MINUTES.toMillis(1), //m
|
||||||
|
TimeUnit.SECONDS.toMillis(1) //s
|
||||||
|
};
|
||||||
|
long millis = -1;
|
||||||
|
for (int i = 0; i < lifeMatch.length; ++i) {
|
||||||
|
Matcher matcher = Pattern.compile("([0-9]+)" + lifeMatch[i]).matcher(input);
|
||||||
|
while (matcher.find()) {
|
||||||
|
long matched = Long.parseLong(matcher.group(1));
|
||||||
|
if (matched < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (millis == -1)
|
||||||
|
millis = 0;
|
||||||
|
|
||||||
|
millis += matched * lifeInterval[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDetailed(long input) {
|
||||||
|
if (input == -1) {
|
||||||
|
return "Permanent";
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatDetailed(input, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDetailed(long input, TimeUnit timeUnit) {
|
||||||
|
if (input == -1) {
|
||||||
|
return "Permanent";
|
||||||
|
}
|
||||||
|
|
||||||
|
long secs = timeUnit.toSeconds(input);
|
||||||
|
|
||||||
|
if (secs == 0) {
|
||||||
|
return "0 seconds";
|
||||||
|
}
|
||||||
|
|
||||||
|
long remainder = secs % 86400;
|
||||||
|
long days = secs / 86400;
|
||||||
|
long hours = remainder / 3600;
|
||||||
|
long minutes = remainder / 60 - hours * 60;
|
||||||
|
long seconds = remainder % 3600 - minutes * 60;
|
||||||
|
String fDays = (days > 0) ? (" " + days + " day" + ((days > 1) ? "s" : "")) : "";
|
||||||
|
String fHours = (hours > 0) ? (" " + hours + " hour" + ((hours > 1) ? "s" : "")) : "";
|
||||||
|
String fMinutes = (minutes > 0) ? (" " + minutes + " minute" + ((minutes > 1) ? "s" : "")) : "";
|
||||||
|
String fSeconds = (seconds > 0) ? (" " + seconds + " second" + ((seconds > 1) ? "s" : "")) : "";
|
||||||
|
return (fDays + fHours + fMinutes + fSeconds).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatTimeAgo(long input) {
|
||||||
|
return formatTimeAgo(input, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatTimeAgo(long input, TimeUnit timeUnit) {
|
||||||
|
long time = System.currentTimeMillis() - timeUnit.toMillis(input);
|
||||||
|
|
||||||
|
if (time >= TimeUnit.DAYS.toMillis(365)) {
|
||||||
|
time = time / TimeUnit.DAYS.toMillis(365);
|
||||||
|
return time + " year" + (time == 1 ? "" : "s") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time >= TimeUnit.DAYS.toMillis(30)) {
|
||||||
|
time = time / TimeUnit.DAYS.toMillis(30);
|
||||||
|
return time + " month" + (time == 1 ? "" : "s") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time >= TimeUnit.DAYS.toMillis(1)) {
|
||||||
|
time = time / TimeUnit.DAYS.toMillis(1);
|
||||||
|
return time + " day" + (time == 1 ? "" : "s") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time >= TimeUnit.HOURS.toMillis(1)) {
|
||||||
|
time = time / TimeUnit.HOURS.toMillis(1);
|
||||||
|
return time + " hour" + (time == 1 ? "" : "s") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time >= TimeUnit.MINUTES.toMillis(1)) {
|
||||||
|
time = time / TimeUnit.MINUTES.toMillis(1);
|
||||||
|
return time + " minute" + (time == 1 ? "" : "s") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time >= TimeUnit.SECONDS.toMillis(1)) {
|
||||||
|
time = time / TimeUnit.SECONDS.toMillis(1);
|
||||||
|
return time + " second" + (time == 1 ? "" : "s") + " ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "now";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatHHMMSS(long input) {
|
||||||
|
return formatHHMMSS(input, false, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatHHMMSS(long input, TimeUnit timeUnit) {
|
||||||
|
return formatHHMMSS(input, false, timeUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatHHMMSS(long input, boolean millis) {
|
||||||
|
return formatHHMMSS(input, millis, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatHHMMSS(long input, boolean displayMillis, TimeUnit timeUnit) {
|
||||||
|
long secs = timeUnit.toSeconds(input);
|
||||||
|
|
||||||
|
if (displayMillis && secs < MINUTE) {
|
||||||
|
long millis = timeUnit.toMillis(input);
|
||||||
|
|
||||||
|
long milliseconds = millis % 1000;
|
||||||
|
millis -= milliseconds;
|
||||||
|
|
||||||
|
long seconds = millis / 1000;
|
||||||
|
return seconds + "." + (milliseconds / 100) + "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
long seconds = secs % 60;
|
||||||
|
secs -= seconds;
|
||||||
|
|
||||||
|
long minutesCount = secs / 60;
|
||||||
|
long minutes = minutesCount % 60L;
|
||||||
|
minutesCount -= minutes;
|
||||||
|
|
||||||
|
long hours = minutesCount / 60L;
|
||||||
|
|
||||||
|
StringBuilder result = TimeUtils.mmssBuilder.get();
|
||||||
|
result.setLength(0);
|
||||||
|
|
||||||
|
if (hours > 0L) {
|
||||||
|
if (hours < 10L)
|
||||||
|
result.append("0");
|
||||||
|
|
||||||
|
result.append(hours);
|
||||||
|
result.append(":");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes < 10L)
|
||||||
|
result.append("0");
|
||||||
|
|
||||||
|
result.append(minutes);
|
||||||
|
result.append(":");
|
||||||
|
|
||||||
|
if (seconds < 10)
|
||||||
|
result.append("0");
|
||||||
|
|
||||||
|
result.append(seconds);
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatTimeShort(long input) {
|
||||||
|
if (input == -1) {
|
||||||
|
return "Permanent";
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatTimeShort(input, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatTimeShort(long input, TimeUnit timeUnit) {
|
||||||
|
if (input == -1) {
|
||||||
|
return "Permanent";
|
||||||
|
}
|
||||||
|
|
||||||
|
long secs = timeUnit.toSeconds(input);
|
||||||
|
|
||||||
|
if (secs == 0) {
|
||||||
|
return "0 seconds";
|
||||||
|
}
|
||||||
|
|
||||||
|
long remainder = secs % 86400;
|
||||||
|
long days = secs / 86400;
|
||||||
|
long hours = remainder / 3600;
|
||||||
|
long minutes = remainder / 60 - hours * 60;
|
||||||
|
long seconds = remainder % 3600 - minutes * 60;
|
||||||
|
String fDays = (days > 0) ? (" " + days + "d") : "";
|
||||||
|
String fHours = (hours > 0) ? (" " + hours + "h") : "";
|
||||||
|
String fMinutes = (minutes > 0) ? (" " + minutes + "m") : "";
|
||||||
|
String fSeconds = (seconds > 0) ? (" " + seconds + "s") : "";
|
||||||
|
return (fDays + fHours + fMinutes + fSeconds).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDate(long input) {
|
||||||
|
return formatDate(input, true, TIME_ZONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDate(long input, boolean time) {
|
||||||
|
return formatDate(input, time, TIME_ZONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDate(long input, TimeZone timeZone) {
|
||||||
|
return formatDate(input, true, timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDate(long input, boolean time, TimeZone timeZone) {
|
||||||
|
if (input == -1) {
|
||||||
|
return "Permanent";
|
||||||
|
}
|
||||||
|
DateFormat formatter = new SimpleDateFormat("MM/dd/yy" + (time ? " hh:mm:ss a" : "") + " z");
|
||||||
|
formatter.setTimeZone(timeZone);
|
||||||
|
return formatter.format(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
94
API/src/main/java/org/hcrival/api/util/UUIDCache.java
Normal file
94
API/src/main/java/org/hcrival/api/util/UUIDCache.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package org.hcrival.api.util;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import org.hcrival.api.InvictusAPI;
|
||||||
|
import org.hcrival.api.util.exception.DataNotFoundException;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class UUIDCache {
|
||||||
|
|
||||||
|
private static final JsonParser JSON_PARSER = new JsonParser();
|
||||||
|
private static final LoadingCache<String, UUID> NAME_UUID_CACHE = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(15L, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<String, UUID>() {
|
||||||
|
@Override
|
||||||
|
public UUID load(String name) throws DataNotFoundException {
|
||||||
|
String response = getResponse("https://api.minetools.eu/uuid/" + name);
|
||||||
|
if (response == null)
|
||||||
|
throw new DataNotFoundException();
|
||||||
|
|
||||||
|
JsonObject parsed = JSON_PARSER.parse(response).getAsJsonObject();
|
||||||
|
String uuid = parsed.get("id").getAsString();
|
||||||
|
|
||||||
|
return UUID.fromString(insertHyphens(uuid));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private static Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0" +
|
||||||
|
"-9a-fA-F]{3}-[0-9a-fA-F]{12}");
|
||||||
|
|
||||||
|
|
||||||
|
public static UUID getUuid(String name) {
|
||||||
|
try {
|
||||||
|
return NAME_UUID_CACHE.get(name);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (!(e.getCause() instanceof DataNotFoundException))
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getName(UUID uuid) {
|
||||||
|
return InvictusAPI.getInstance().getRedisService().executeCommand(redis -> {
|
||||||
|
if (!redis.hexists("UUIDCache", uuid.toString()))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return redis.hget("UUIDCache", uuid.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUuid(String input) {
|
||||||
|
return UUID_PATTERN.matcher(input).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getResponse(String urlString) {
|
||||||
|
try {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(urlString).openConnection();
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
new BufferedReader(new InputStreamReader(connection.getInputStream())).lines().forEach(response::append);
|
||||||
|
return response.toString();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String insertHyphens(String uuid) {
|
||||||
|
StringBuilder sb = new StringBuilder(uuid);
|
||||||
|
sb.insert(8, "-");
|
||||||
|
sb = new StringBuilder(sb.toString());
|
||||||
|
sb.insert(13, "-");
|
||||||
|
sb = new StringBuilder(sb.toString());
|
||||||
|
sb.insert(18, "-");
|
||||||
|
sb = new StringBuilder(sb.toString());
|
||||||
|
sb.insert(23, "-");
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.hcrival.api.util.configuration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 12.02.2020 / 21:05
|
||||||
|
* iLib / cc.invictusgames.ilib.configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface ConfigurationService {
|
||||||
|
|
||||||
|
void saveConfiguration(StaticConfiguration configuration, File file) throws IOException;
|
||||||
|
|
||||||
|
<T extends StaticConfiguration> T loadConfiguration(Class<? extends T> clazz, File file);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.hcrival.api.util.configuration;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.hcrival.api.util.Statics;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 12.02.2020 / 21:05
|
||||||
|
* iLib / cc.invictusgames.ilib.configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class JsonConfigurationService implements ConfigurationService {
|
||||||
|
|
||||||
|
@Setter public static Gson gson = Statics.GSON;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveConfiguration(StaticConfiguration configuration, File file) {
|
||||||
|
try {
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||||
|
gson.toJson(configuration, writer);
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Failed to save configuration " + configuration.getClass().getName() + " to file " + file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends StaticConfiguration> T loadConfiguration(Class<? extends T> clazz, File file) {
|
||||||
|
if ((!file.getParentFile().exists()) && (!file.getParentFile().mkdir())) {
|
||||||
|
System.err.println("Failed to create parent folder for " + file.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
T config = clazz.newInstance();
|
||||||
|
if (!file.exists()) {
|
||||||
|
if (!file.createNewFile()) {
|
||||||
|
System.err.println("Failed to create file for " + file.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
saveConfiguration(config, file);
|
||||||
|
}
|
||||||
|
} catch (InstantiationException | IllegalAccessException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
T config = gson.fromJson(new BufferedReader(new FileReader(file)), clazz);
|
||||||
|
saveConfiguration(config, file);
|
||||||
|
return config;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
System.err.println("Failed to load configuration " + clazz.getName() + " from file " + file.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.hcrival.api.util.configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 12.02.2020 / 21:06
|
||||||
|
* iLib / cc.invictusgames.ilib.configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface StaticConfiguration {
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.hcrival.api.util.configuration.defaults;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hcrival.api.util.configuration.StaticConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 12.02.2020 / 21:29
|
||||||
|
* iLib / cc.invictusgames.ilib.configuration.defaults
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public class MongoConfig implements StaticConfiguration {
|
||||||
|
|
||||||
|
private String host = "localhost";
|
||||||
|
private int port = 27017;
|
||||||
|
private boolean authEnabled = false;
|
||||||
|
private String authUsername = "username";
|
||||||
|
private String authPassword = "password";
|
||||||
|
private String authDatabase = "admin";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package org.hcrival.api.util.configuration.defaults;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hcrival.api.util.configuration.StaticConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emilxyz (langgezockt@gmail.com)
|
||||||
|
* 12.02.2020 / 21:33
|
||||||
|
* iLib / cc.invictusgames.ilib.configuration.defaults
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public class RedisConfig implements StaticConfiguration {
|
||||||
|
|
||||||
|
private String host = "localhost";
|
||||||
|
private int port = 6379;
|
||||||
|
private boolean authEnabled = false;
|
||||||
|
private String authPassword = "password";
|
||||||
|
private int dbId = 0;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package org.hcrival.api.util.exception;
|
||||||
|
|
||||||
|
public class DataNotFoundException extends Exception {
|
||||||
|
}
|
1
API/src/main/resources/application.properties
Normal file
1
API/src/main/resources/application.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
spring.mvc.converters.preferred-json-mapper=gson
|
5
Atticus/.gitignore
vendored
Normal file
5
Atticus/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Project exclude paths
|
||||||
|
/Atticus-API/target/
|
||||||
|
/Atticus-Server/target/
|
||||||
|
*.xml
|
||||||
|
Atticus-Server/dependency-reduced-pom.xml
|
8
Atticus/.idea/.gitignore
vendored
Normal file
8
Atticus/.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
@ -0,0 +1,79 @@
|
|||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import static co.aikar.timings.TimingsManager.*;
|
||||||
|
|
||||||
|
public class FullServerTickHandler extends TimingHandler {
|
||||||
|
static final TimingIdentifier IDENTITY = new TimingIdentifier("Minecraft", "Full Server Tick", null, false);
|
||||||
|
final TimingData minuteData;
|
||||||
|
double avgFreeMemory = -1D;
|
||||||
|
double avgUsedMemory = -1D;
|
||||||
|
FullServerTickHandler() {
|
||||||
|
super(IDENTITY);
|
||||||
|
minuteData = new TimingData(id);
|
||||||
|
|
||||||
|
TIMING_MAP.put(IDENTITY, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startTiming() {
|
||||||
|
if (TimingsManager.needsFullReset) {
|
||||||
|
TimingsManager.resetTimings();
|
||||||
|
} else if (TimingsManager.needsRecheckEnabled) {
|
||||||
|
TimingsManager.recheckEnabled();
|
||||||
|
}
|
||||||
|
super.startTiming();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopTiming() {
|
||||||
|
super.stopTiming();
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TimingHistory.timedTicks % 20 == 0) {
|
||||||
|
final Runtime runtime = Runtime.getRuntime();
|
||||||
|
double usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||||
|
double freeMemory = runtime.maxMemory() - usedMemory;
|
||||||
|
if (this.avgFreeMemory == -1) {
|
||||||
|
this.avgFreeMemory = freeMemory;
|
||||||
|
} else {
|
||||||
|
this.avgFreeMemory = (this.avgFreeMemory * (59 / 60D)) + (freeMemory * (1 / 60D));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.avgUsedMemory == -1) {
|
||||||
|
this.avgUsedMemory = usedMemory;
|
||||||
|
} else {
|
||||||
|
this.avgUsedMemory = (this.avgUsedMemory * (59 / 60D)) + (usedMemory * (1 / 60D));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = System.nanoTime();
|
||||||
|
TimingsManager.tick();
|
||||||
|
long diff = System.nanoTime() - start;
|
||||||
|
CURRENT = TIMINGS_TICK;
|
||||||
|
TIMINGS_TICK.addDiff(diff);
|
||||||
|
// addDiff for TIMINGS_TICK incremented this, bring it back down to 1 per tick.
|
||||||
|
record.curTickCount--;
|
||||||
|
minuteData.curTickTotal = record.curTickTotal;
|
||||||
|
minuteData.curTickCount = 1;
|
||||||
|
boolean violated = isViolated();
|
||||||
|
minuteData.processTick(violated);
|
||||||
|
TIMINGS_TICK.processTick(violated);
|
||||||
|
processTick(violated);
|
||||||
|
|
||||||
|
|
||||||
|
if (TimingHistory.timedTicks % 1200 == 0) {
|
||||||
|
MINUTE_REPORTS.add(new TimingHistory.MinuteReport());
|
||||||
|
TimingHistory.resetTicks(false);
|
||||||
|
minuteData.reset();
|
||||||
|
}
|
||||||
|
if (TimingHistory.timedTicks % Timings.getHistoryInterval() == 0) {
|
||||||
|
TimingsManager.HISTORY.add(new TimingHistory());
|
||||||
|
TimingsManager.resetTimings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isViolated() {
|
||||||
|
return record.curTickTotal > 50000000;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
public final class NullTimingHandler implements Timing {
|
||||||
|
@Override
|
||||||
|
public void startTiming() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopTiming() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startTimingIfSync() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopTimingIfSync() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimingHandler getTimingHandler() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.EventException;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.plugin.EventExecutor;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class TimedEventExecutor implements EventExecutor {
|
||||||
|
|
||||||
|
private final EventExecutor executor;
|
||||||
|
private final Timing timings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an event executor and associates a timing handler to it.
|
||||||
|
*
|
||||||
|
* @param executor
|
||||||
|
* @param plugin
|
||||||
|
* @param method
|
||||||
|
* @param eventClass
|
||||||
|
*/
|
||||||
|
public TimedEventExecutor(EventExecutor executor, Plugin plugin, Method method, Class<? extends Event> eventClass) {
|
||||||
|
this.executor = executor;
|
||||||
|
String id;
|
||||||
|
|
||||||
|
if (method == null) {
|
||||||
|
if (executor.getClass().getEnclosingClass() != null) { // Oh Skript, how we love you
|
||||||
|
method = executor.getClass().getEnclosingMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method != null) {
|
||||||
|
id = method.getDeclaringClass().getName();
|
||||||
|
} else {
|
||||||
|
id = executor.getClass().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final String eventName = eventClass.getSimpleName();
|
||||||
|
boolean verbose = "BlockPhysicsEvent".equals(eventName) || "Drain".equals(eventName) || "Fill".equals(eventName);
|
||||||
|
this.timings = Timings.ofSafe(plugin.getName(), (verbose ? "## " : "") +
|
||||||
|
"Event: " + id + " (" + eventName + ")", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Listener listener, Event event) throws EventException {
|
||||||
|
if (event.isAsynchronous() || !Timings.timingsEnabled || !Bukkit.isPrimaryThread()) {
|
||||||
|
executor.execute(listener, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timings.startTiming();
|
||||||
|
executor.execute(listener, event);
|
||||||
|
timings.stopTiming();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an ability to time sections of code within the Minecraft Server
|
||||||
|
*/
|
||||||
|
public interface Timing extends AutoCloseable {
|
||||||
|
/**
|
||||||
|
* Starts timing the execution until {@link #stopTiming()} is called.
|
||||||
|
*/
|
||||||
|
public void startTiming();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Stops timing and records the data. Propagates the data up to group handlers.</p>
|
||||||
|
*
|
||||||
|
* Will automatically be called when this Timing is used with try-with-resources
|
||||||
|
*/
|
||||||
|
public void stopTiming();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts timing the execution until {@link #stopTiming()} is called.
|
||||||
|
*
|
||||||
|
* But only if we are on the primary thread.
|
||||||
|
*/
|
||||||
|
public void startTimingIfSync();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Stops timing and records the data. Propagates the data up to group handlers.</p>
|
||||||
|
*
|
||||||
|
* <p>Will automatically be called when this Timing is used with try-with-resources</p>
|
||||||
|
*
|
||||||
|
* But only if we are on the primary thread.
|
||||||
|
*/
|
||||||
|
public void stopTimingIfSync();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops timing and disregards current timing data.
|
||||||
|
*/
|
||||||
|
public void abort();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally to get the actual backing Handler in the case of delegated Handlers
|
||||||
|
*
|
||||||
|
* @return TimingHandler
|
||||||
|
*/
|
||||||
|
TimingHandler getTimingHandler();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static co.aikar.util.JSONUtil.toArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Lightweight object for tracking timing data</p>
|
||||||
|
*
|
||||||
|
* This is broken out to reduce memory usage
|
||||||
|
*/
|
||||||
|
class TimingData {
|
||||||
|
static Function<Integer, TimingData> LOADER = new Function<Integer, TimingData>() {
|
||||||
|
@Override
|
||||||
|
public TimingData apply(Integer input) {
|
||||||
|
return new TimingData(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
int id;
|
||||||
|
int count = 0;
|
||||||
|
int lagCount = 0;
|
||||||
|
long totalTime = 0;
|
||||||
|
long lagTotalTime = 0;
|
||||||
|
|
||||||
|
int curTickCount = 0;
|
||||||
|
int curTickTotal = 0;
|
||||||
|
|
||||||
|
TimingData(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingData(TimingData data) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.totalTime = data.totalTime;
|
||||||
|
this.lagTotalTime = data.lagTotalTime;
|
||||||
|
this.count = data.count;
|
||||||
|
this.lagCount = data.lagCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(long diff) {
|
||||||
|
++curTickCount;
|
||||||
|
curTickTotal += diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processTick(boolean violated) {
|
||||||
|
totalTime += curTickTotal;
|
||||||
|
count += curTickCount;
|
||||||
|
if (violated) {
|
||||||
|
lagTotalTime += curTickTotal;
|
||||||
|
lagCount += curTickCount;
|
||||||
|
}
|
||||||
|
curTickTotal = 0;
|
||||||
|
curTickCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
count = 0;
|
||||||
|
lagCount = 0;
|
||||||
|
curTickTotal = 0;
|
||||||
|
curTickCount = 0;
|
||||||
|
totalTime = 0;
|
||||||
|
lagTotalTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TimingData clone() {
|
||||||
|
return new TimingData(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List export() {
|
||||||
|
List list = toArray(
|
||||||
|
id,
|
||||||
|
count,
|
||||||
|
totalTime);
|
||||||
|
if (lagCount > 0) {
|
||||||
|
list.add(lagCount);
|
||||||
|
list.add(lagTotalTime);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import gnu.trove.map.hash.TIntObjectHashMap;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import co.aikar.util.LoadingIntMap;
|
||||||
|
import co.aikar.util.LoadingMap;
|
||||||
|
import co.aikar.util.MRUMapCache;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
class TimingHandler implements Timing {
|
||||||
|
|
||||||
|
private static int idPool = 1;
|
||||||
|
final int id = idPool++;
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final boolean verbose;
|
||||||
|
|
||||||
|
final TIntObjectHashMap<TimingData> children = new LoadingIntMap<TimingData>(TimingData.LOADER);
|
||||||
|
|
||||||
|
final TimingData record;
|
||||||
|
final TimingHandler groupHandler;
|
||||||
|
|
||||||
|
long start = 0;
|
||||||
|
int timingDepth = 0;
|
||||||
|
boolean added;
|
||||||
|
boolean timed;
|
||||||
|
boolean enabled;
|
||||||
|
TimingHandler parent;
|
||||||
|
|
||||||
|
TimingHandler(TimingIdentifier id) {
|
||||||
|
if (id.name.startsWith("##")) {
|
||||||
|
verbose = true;
|
||||||
|
this.name = id.name.substring(3);
|
||||||
|
} else {
|
||||||
|
this.name = id.name;
|
||||||
|
verbose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.record = new TimingData(this.id);
|
||||||
|
this.groupHandler = id.groupHandler;
|
||||||
|
|
||||||
|
TimingIdentifier.getGroup(id.group).handlers.add(this);
|
||||||
|
checkEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
final void checkEnabled() {
|
||||||
|
enabled = Timings.timingsEnabled && (!verbose || Timings.verboseEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void processTick(boolean violated) {
|
||||||
|
if (timingDepth != 0 || record.curTickCount == 0) {
|
||||||
|
timingDepth = 0;
|
||||||
|
start = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
record.processTick(violated);
|
||||||
|
for (TimingData handler : children.valueCollection()) {
|
||||||
|
handler.processTick(violated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startTimingIfSync() {
|
||||||
|
if (Bukkit.isPrimaryThread()) {
|
||||||
|
startTiming();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopTimingIfSync() {
|
||||||
|
if (Bukkit.isPrimaryThread()) {
|
||||||
|
stopTiming();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTiming() {
|
||||||
|
if (enabled && ++timingDepth == 1) {
|
||||||
|
start = System.nanoTime();
|
||||||
|
parent = TimingsManager.CURRENT;
|
||||||
|
TimingsManager.CURRENT = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopTiming() {
|
||||||
|
if (enabled && --timingDepth == 0 && start != 0) {
|
||||||
|
if (!Bukkit.isPrimaryThread()) {
|
||||||
|
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||||
|
new Throwable().printStackTrace();
|
||||||
|
start = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDiff(System.nanoTime() - start);
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort() {
|
||||||
|
if (enabled && timingDepth > 0) {
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addDiff(long diff) {
|
||||||
|
if (TimingsManager.CURRENT == this) {
|
||||||
|
TimingsManager.CURRENT = parent;
|
||||||
|
if (parent != null) {
|
||||||
|
parent.children.get(id).add(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record.add(diff);
|
||||||
|
if (!added) {
|
||||||
|
added = true;
|
||||||
|
timed = true;
|
||||||
|
TimingsManager.HANDLERS.add(this);
|
||||||
|
}
|
||||||
|
if (groupHandler != null) {
|
||||||
|
groupHandler.addDiff(diff);
|
||||||
|
groupHandler.children.get(id).add(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset this timer, setting all values to zero.
|
||||||
|
*
|
||||||
|
* @param full
|
||||||
|
*/
|
||||||
|
void reset(boolean full) {
|
||||||
|
record.reset();
|
||||||
|
if (full) {
|
||||||
|
timed = false;
|
||||||
|
}
|
||||||
|
start = 0;
|
||||||
|
timingDepth = 0;
|
||||||
|
added = false;
|
||||||
|
children.clear();
|
||||||
|
checkEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimingHandler getTimingHandler() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return (this == o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is simply for the Closeable interface so it can be used with
|
||||||
|
* try-with-resources ()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
stopTimingIfSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSpecial() {
|
||||||
|
return this == TimingsManager.FULL_SERVER_TICK || this == TimingsManager.TIMINGS_TICK;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import co.aikar.util.LoadingMap;
|
||||||
|
import co.aikar.util.MRUMapCache;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static co.aikar.timings.TimingsManager.FULL_SERVER_TICK;
|
||||||
|
import static co.aikar.timings.TimingsManager.MINUTE_REPORTS;
|
||||||
|
import static co.aikar.util.JSONUtil.*;
|
||||||
|
|
||||||
|
@SuppressWarnings({"deprecation", "SuppressionAnnotation"})
|
||||||
|
public class TimingHistory {
|
||||||
|
public static long lastMinuteTime;
|
||||||
|
public static long timedTicks;
|
||||||
|
public static long playerTicks;
|
||||||
|
public static long entityTicks;
|
||||||
|
public static long tileEntityTicks;
|
||||||
|
public static long activatedEntityTicks;
|
||||||
|
static int worldIdPool = 1;
|
||||||
|
static Map<String, Integer> worldMap = LoadingMap.newHashMap(new Function<String, Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer apply(String input) {
|
||||||
|
return worldIdPool++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final long endTime;
|
||||||
|
final long startTime;
|
||||||
|
final long totalTicks;
|
||||||
|
final long totalTime; // Represents all time spent running the server this history
|
||||||
|
final MinuteReport[] minuteReports;
|
||||||
|
|
||||||
|
final TimingHistoryEntry[] entries;
|
||||||
|
final Set<Material> tileEntityTypeSet = Sets.newHashSet();
|
||||||
|
final Set<EntityType> entityTypeSet = Sets.newHashSet();
|
||||||
|
final Map<Object, Object> worlds;
|
||||||
|
|
||||||
|
TimingHistory() {
|
||||||
|
this.endTime = System.currentTimeMillis() / 1000;
|
||||||
|
this.startTime = TimingsManager.historyStart / 1000;
|
||||||
|
if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) {
|
||||||
|
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]);
|
||||||
|
this.minuteReports[this.minuteReports.length - 1] = new MinuteReport();
|
||||||
|
} else {
|
||||||
|
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size()]);
|
||||||
|
}
|
||||||
|
long ticks = 0;
|
||||||
|
for (MinuteReport mp : this.minuteReports) {
|
||||||
|
ticks += mp.ticksRecord.timed;
|
||||||
|
}
|
||||||
|
this.totalTicks = ticks;
|
||||||
|
this.totalTime = FULL_SERVER_TICK.record.totalTime;
|
||||||
|
this.entries = new TimingHistoryEntry[TimingsManager.HANDLERS.size()];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (TimingHandler handler : TimingsManager.HANDLERS) {
|
||||||
|
entries[i++] = new TimingHistoryEntry(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<EntityType, Counter> entityCounts = MRUMapCache.of(LoadingMap.of(
|
||||||
|
new EnumMap<EntityType, Counter>(EntityType.class), Counter.LOADER
|
||||||
|
));
|
||||||
|
final Map<Material, Counter> tileEntityCounts = MRUMapCache.of(LoadingMap.of(
|
||||||
|
new EnumMap<Material, Counter>(Material.class), Counter.LOADER
|
||||||
|
));
|
||||||
|
// Information about all loaded chunks/entities
|
||||||
|
this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function<World, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(World world) {
|
||||||
|
return pair(
|
||||||
|
worldMap.get(world.getName()),
|
||||||
|
toArrayMapper(world.getLoadedChunks(), new Function<Chunk, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(Chunk chunk) {
|
||||||
|
entityCounts.clear();
|
||||||
|
tileEntityCounts.clear();
|
||||||
|
|
||||||
|
for (Entity entity : chunk.getEntities()) {
|
||||||
|
entityCounts.get(entity.getType()).increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BlockState tileEntity : chunk.getTileEntities()) {
|
||||||
|
tileEntityCounts.get(tileEntity.getBlock().getType()).increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tileEntityCounts.isEmpty() && entityCounts.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toArray(
|
||||||
|
chunk.getX(),
|
||||||
|
chunk.getZ(),
|
||||||
|
toObjectMapper(entityCounts.entrySet(),
|
||||||
|
new Function<Map.Entry<EntityType, Counter>, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Map.Entry<EntityType, Counter> entry) {
|
||||||
|
entityTypeSet.add(entry.getKey());
|
||||||
|
return pair(
|
||||||
|
String.valueOf(entry.getKey().getTypeId()),
|
||||||
|
entry.getValue().count()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
toObjectMapper(tileEntityCounts.entrySet(),
|
||||||
|
new Function<Map.Entry<Material, Counter>, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Map.Entry<Material, Counter> entry) {
|
||||||
|
tileEntityTypeSet.add(entry.getKey());
|
||||||
|
return pair(
|
||||||
|
String.valueOf(entry.getKey().getId()),
|
||||||
|
entry.getValue().count()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetTicks(boolean fullReset) {
|
||||||
|
if (fullReset) {
|
||||||
|
// Non full is simply for 1 minute reports
|
||||||
|
timedTicks = 0;
|
||||||
|
}
|
||||||
|
lastMinuteTime = System.nanoTime();
|
||||||
|
playerTicks = 0;
|
||||||
|
tileEntityTicks = 0;
|
||||||
|
entityTicks = 0;
|
||||||
|
activatedEntityTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object export() {
|
||||||
|
return createObject(
|
||||||
|
pair("s", startTime),
|
||||||
|
pair("e", endTime),
|
||||||
|
pair("tk", totalTicks),
|
||||||
|
pair("tm", totalTime),
|
||||||
|
pair("w", worlds),
|
||||||
|
pair("h", toArrayMapper(entries, new Function<TimingHistoryEntry, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(TimingHistoryEntry entry) {
|
||||||
|
TimingData record = entry.data;
|
||||||
|
if (record.count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entry.export();
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
pair("mp", toArrayMapper(minuteReports, new Function<MinuteReport, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(MinuteReport input) {
|
||||||
|
return input.export();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MinuteReport {
|
||||||
|
final long time = System.currentTimeMillis() / 1000;
|
||||||
|
|
||||||
|
final TicksRecord ticksRecord = new TicksRecord();
|
||||||
|
final PingRecord pingRecord = new PingRecord();
|
||||||
|
final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone();
|
||||||
|
final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed;
|
||||||
|
final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory;
|
||||||
|
final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory;
|
||||||
|
final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
|
||||||
|
|
||||||
|
public List export() {
|
||||||
|
return toArray(
|
||||||
|
time,
|
||||||
|
Math.round(tps * 100D) / 100D,
|
||||||
|
Math.round(pingRecord.avg * 100D) / 100D,
|
||||||
|
fst.export(),
|
||||||
|
toArray(ticksRecord.timed,
|
||||||
|
ticksRecord.player,
|
||||||
|
ticksRecord.entity,
|
||||||
|
ticksRecord.activatedEntity,
|
||||||
|
ticksRecord.tileEntity
|
||||||
|
),
|
||||||
|
usedMemory,
|
||||||
|
freeMemory,
|
||||||
|
loadAvg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TicksRecord {
|
||||||
|
final long timed;
|
||||||
|
final long player;
|
||||||
|
final long entity;
|
||||||
|
final long tileEntity;
|
||||||
|
final long activatedEntity;
|
||||||
|
|
||||||
|
TicksRecord() {
|
||||||
|
timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200);
|
||||||
|
player = playerTicks;
|
||||||
|
entity = entityTicks;
|
||||||
|
tileEntity = tileEntityTicks;
|
||||||
|
activatedEntity = activatedEntityTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PingRecord {
|
||||||
|
final double avg;
|
||||||
|
|
||||||
|
PingRecord() {
|
||||||
|
final Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
||||||
|
int totalPing = 0;
|
||||||
|
for (Player player : onlinePlayers) {
|
||||||
|
totalPing += player.spigot().getPing();
|
||||||
|
}
|
||||||
|
avg = onlinePlayers.isEmpty() ? 0 : totalPing / onlinePlayers.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Counter {
|
||||||
|
int count = 0;
|
||||||
|
@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
|
||||||
|
static Function LOADER = new LoadingMap.Feeder<Counter>() {
|
||||||
|
@Override
|
||||||
|
public Counter apply() {
|
||||||
|
return new Counter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public int increment() {
|
||||||
|
return ++count;
|
||||||
|
}
|
||||||
|
public int count() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static co.aikar.util.JSONUtil.toArrayMapper;
|
||||||
|
|
||||||
|
class TimingHistoryEntry {
|
||||||
|
final TimingData data;
|
||||||
|
final TimingData[] children;
|
||||||
|
|
||||||
|
TimingHistoryEntry(TimingHandler handler) {
|
||||||
|
this.data = handler.record.clone();
|
||||||
|
children = new TimingData[handler.children.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (TimingData child : handler.children.valueCollection()) {
|
||||||
|
children[i++] = child.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List export() {
|
||||||
|
List result = data.export();
|
||||||
|
if (children.length > 0) {
|
||||||
|
result.add(
|
||||||
|
toArrayMapper(children, new Function<TimingData, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(TimingData child) {
|
||||||
|
return child.export();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import co.aikar.util.LoadingMap;
|
||||||
|
import co.aikar.util.MRUMapCache;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Used as a basis for fast HashMap key comparisons for the Timing Map.</p>
|
||||||
|
*
|
||||||
|
* This class uses interned strings giving us the ability to do an identity check instead of equals() on the strings
|
||||||
|
*/
|
||||||
|
final class TimingIdentifier {
|
||||||
|
/**
|
||||||
|
* Holds all groups. Autoloads on request for a group by name.
|
||||||
|
*/
|
||||||
|
static final Map<String, TimingGroup> GROUP_MAP = MRUMapCache.of(
|
||||||
|
LoadingMap.newIdentityHashMap(new Function<String, TimingGroup>() {
|
||||||
|
@Override
|
||||||
|
public TimingGroup apply(String group) {
|
||||||
|
return new TimingGroup(group);
|
||||||
|
}
|
||||||
|
}, 64)
|
||||||
|
);
|
||||||
|
static final TimingGroup DEFAULT_GROUP = getGroup("Minecraft");
|
||||||
|
final String group;
|
||||||
|
final String name;
|
||||||
|
final TimingHandler groupHandler;
|
||||||
|
final boolean protect;
|
||||||
|
private final int hashCode;
|
||||||
|
|
||||||
|
TimingIdentifier(String group, String name, Timing groupHandler, boolean protect) {
|
||||||
|
this.group = group != null ? group.intern() : DEFAULT_GROUP.name;
|
||||||
|
this.name = name.intern();
|
||||||
|
this.groupHandler = groupHandler != null ? groupHandler.getTimingHandler() : null;
|
||||||
|
this.protect = protect;
|
||||||
|
this.hashCode = (31 * this.group.hashCode()) + this.name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimingGroup getGroup(String groupName) {
|
||||||
|
if (groupName == null) {
|
||||||
|
return DEFAULT_GROUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GROUP_MAP.get(groupName.intern());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are using .intern() on the strings so it is guaranteed to be an identity comparison.
|
||||||
|
@SuppressWarnings("StringEquality")
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingIdentifier that = (TimingIdentifier) o;
|
||||||
|
return group == that.group && name == that.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TimingGroup {
|
||||||
|
|
||||||
|
private static int idPool = 1;
|
||||||
|
final int id = idPool++;
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
ArrayDeque<TimingHandler> handlers = new ArrayDeque<TimingHandler>(64);
|
||||||
|
|
||||||
|
private TimingGroup(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
273
Atticus/Atticus-API/src/main/java/co/aikar/timings/Timings.java
Normal file
273
Atticus/Atticus-API/src/main/java/co/aikar/timings/Timings.java
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.EvictingQueue;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
public final class Timings {
|
||||||
|
|
||||||
|
private static final int MAX_HISTORY_FRAMES = 12;
|
||||||
|
public static final Timing NULL_HANDLER = new NullTimingHandler();
|
||||||
|
static boolean timingsEnabled = false;
|
||||||
|
static boolean verboseEnabled = false;
|
||||||
|
private static int historyInterval = -1;
|
||||||
|
private static int historyLength = -1;
|
||||||
|
|
||||||
|
private Timings() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Timing for a plugin corresponding to a name.
|
||||||
|
*
|
||||||
|
* @param plugin Plugin to own the Timing
|
||||||
|
* @param name Name of Timing
|
||||||
|
* @return Handler
|
||||||
|
*/
|
||||||
|
public static Timing of(Plugin plugin, String name) {
|
||||||
|
Timing pluginHandler = null;
|
||||||
|
if (plugin != null) {
|
||||||
|
pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER);
|
||||||
|
}
|
||||||
|
return of(plugin, name, pluginHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns a handler that has a groupHandler timer handler. Parent timers should not have their
|
||||||
|
* start/stop methods called directly, as the children will call it for you.</p>
|
||||||
|
*
|
||||||
|
* Parent Timers are used to group multiple subsections together and get a summary of them combined
|
||||||
|
* Parent Handler can not be changed after first call
|
||||||
|
*
|
||||||
|
* @param plugin Plugin to own the Timing
|
||||||
|
* @param name Name of Timing
|
||||||
|
* @param groupHandler Parent handler to mirror .start/stop calls to
|
||||||
|
* @return Timing Handler
|
||||||
|
*/
|
||||||
|
public static Timing of(Plugin plugin, String name, Timing groupHandler) {
|
||||||
|
Preconditions.checkNotNull(plugin, "Plugin can not be null");
|
||||||
|
return TimingsManager.getHandler(plugin.getName(), name, groupHandler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Timing object after starting it, useful for Java7 try-with-resources.
|
||||||
|
*
|
||||||
|
* try (Timing ignored = Timings.ofStart(plugin, someName)) {
|
||||||
|
* // timed section
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param plugin Plugin to own the Timing
|
||||||
|
* @param name Name of Timing
|
||||||
|
* @return Timing Handler
|
||||||
|
*/
|
||||||
|
public static Timing ofStart(Plugin plugin, String name) {
|
||||||
|
return ofStart(plugin, name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Timing object after starting it, useful for Java7 try-with-resources.
|
||||||
|
*
|
||||||
|
* try (Timing ignored = Timings.ofStart(plugin, someName, groupHandler)) {
|
||||||
|
* // timed section
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param plugin Plugin to own the Timing
|
||||||
|
* @param name Name of Timing
|
||||||
|
* @param groupHandler Parent handler to mirror .start/stop calls to
|
||||||
|
* @return Timing Handler
|
||||||
|
*/
|
||||||
|
public static Timing ofStart(Plugin plugin, String name, Timing groupHandler) {
|
||||||
|
Timing timing = of(plugin, name, groupHandler);
|
||||||
|
timing.startTimingIfSync();
|
||||||
|
return timing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether or not the Spigot Timings system is enabled
|
||||||
|
*
|
||||||
|
* @return Enabled or not
|
||||||
|
*/
|
||||||
|
public static boolean isTimingsEnabled() {
|
||||||
|
return timingsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets whether or not the Spigot Timings system should be enabled</p>
|
||||||
|
*
|
||||||
|
* Calling this will reset timing data.
|
||||||
|
*
|
||||||
|
* @param enabled Should timings be reported
|
||||||
|
*/
|
||||||
|
public static void setTimingsEnabled(boolean enabled) {
|
||||||
|
timingsEnabled = enabled;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets whether or not the Timings should monitor at Verbose level.</p>
|
||||||
|
*
|
||||||
|
* <p>When Verbose is disabled, high-frequency timings will not be available.</p>
|
||||||
|
*
|
||||||
|
* @return Enabled or not
|
||||||
|
*/
|
||||||
|
public static boolean isVerboseTimingsEnabled() {
|
||||||
|
return timingsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not the Timings should monitor at Verbose level.
|
||||||
|
* <p/>
|
||||||
|
* When Verbose is disabled, high-frequency timings will not be available.
|
||||||
|
* Calling this will reset timing data.
|
||||||
|
*
|
||||||
|
* @param enabled Should high-frequency timings be reported
|
||||||
|
*/
|
||||||
|
public static void setVerboseTimingsEnabled(boolean enabled) {
|
||||||
|
verboseEnabled = enabled;
|
||||||
|
TimingsManager.needsRecheckEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Gets the interval between Timing History report generation.</p>
|
||||||
|
*
|
||||||
|
* Defaults to 5 minutes (6000 ticks)
|
||||||
|
*
|
||||||
|
* @return Interval in ticks
|
||||||
|
*/
|
||||||
|
public static int getHistoryInterval() {
|
||||||
|
return historyInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the interval between Timing History report generations.</p>
|
||||||
|
*
|
||||||
|
* <p>Defaults to 5 minutes (6000 ticks)</p>
|
||||||
|
*
|
||||||
|
* This will recheck your history length, so lowering this value will lower your
|
||||||
|
* history length if you need more than 60 history windows.
|
||||||
|
*
|
||||||
|
* @param interval Interval in ticks
|
||||||
|
*/
|
||||||
|
public static void setHistoryInterval(int interval) {
|
||||||
|
historyInterval = Math.max(20*60, interval);
|
||||||
|
// Recheck the history length with the new Interval
|
||||||
|
if (historyLength != -1) {
|
||||||
|
setHistoryLength(historyLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets how long in ticks Timings history is kept for the server.
|
||||||
|
*
|
||||||
|
* Defaults to 1 hour (72000 ticks)
|
||||||
|
*
|
||||||
|
* @return Duration in Ticks
|
||||||
|
*/
|
||||||
|
public static int getHistoryLength() {
|
||||||
|
return historyLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how long Timing History reports are kept for the server.
|
||||||
|
*
|
||||||
|
* Defaults to 1 hours(72000 ticks)
|
||||||
|
*
|
||||||
|
* This value is capped at a maximum of getHistoryInterval() * MAX_HISTORY_FRAMES (12)
|
||||||
|
*
|
||||||
|
* Will not reset Timing Data but may truncate old history if the new length is less than old length.
|
||||||
|
*
|
||||||
|
* @param length Duration in ticks
|
||||||
|
*/
|
||||||
|
public static void setHistoryLength(int length) {
|
||||||
|
// Cap at 12 History Frames, 1 hour at 5 minute frames.
|
||||||
|
int maxLength = historyInterval * MAX_HISTORY_FRAMES;
|
||||||
|
// For special cases of servers with special permission to bypass the max.
|
||||||
|
// This max helps keep data file sizes reasonable for processing on Aikar's Timing parser side.
|
||||||
|
// Setting this will not help you bypass the max unless Aikar has added an exception on the API side.
|
||||||
|
if (System.getProperty("timings.bypassMax") != null) {
|
||||||
|
maxLength = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
historyLength = Math.max(Math.min(maxLength, length), historyInterval);
|
||||||
|
Queue<TimingHistory> oldQueue = TimingsManager.HISTORY;
|
||||||
|
int frames = (getHistoryLength() / getHistoryInterval());
|
||||||
|
if (length > maxLength) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "Timings Length too high. Requested " + length + ", max is " + maxLength + ". To get longer history, you must increase your interval. Set Interval to " + Math.ceil(length / MAX_HISTORY_FRAMES) + " to achieve this length.");
|
||||||
|
}
|
||||||
|
TimingsManager.HISTORY = EvictingQueue.create(frames);
|
||||||
|
TimingsManager.HISTORY.addAll(oldQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all Timing Data
|
||||||
|
*/
|
||||||
|
public static void reset() {
|
||||||
|
TimingsManager.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a report and sends it to the specified command sender.
|
||||||
|
*
|
||||||
|
* If sender is null, ConsoleCommandSender will be used.
|
||||||
|
* @param sender The sender to send to, or null to use the ConsoleCommandSender
|
||||||
|
*/
|
||||||
|
public static void generateReport(CommandSender sender) {
|
||||||
|
if (sender == null) {
|
||||||
|
sender = Bukkit.getConsoleSender();
|
||||||
|
}
|
||||||
|
TimingsExport.reportTimings(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
=================
|
||||||
|
Protected API: These are for internal use only in Bukkit/CraftBukkit
|
||||||
|
These do not have isPrimaryThread() checks in the startTiming/stopTiming
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
|
||||||
|
static TimingHandler ofSafe(String name) {
|
||||||
|
return ofSafe(null, name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Timing ofSafe(Plugin plugin, String name) {
|
||||||
|
Timing pluginHandler = null;
|
||||||
|
if (plugin != null) {
|
||||||
|
pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER);
|
||||||
|
}
|
||||||
|
return ofSafe(plugin != null ? plugin.getName() : "Minecraft - Invalid Plugin", name, pluginHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimingHandler ofSafe(String name, Timing groupHandler) {
|
||||||
|
return ofSafe(null, name, groupHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimingHandler ofSafe(String groupName, String name, Timing groupHandler) {
|
||||||
|
return TimingsManager.getHandler(groupName, name, groupHandler, false);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import org.apache.commons.lang.Validate;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.defaults.BukkitCommand;
|
||||||
|
import org.bukkit.util.StringUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class TimingsCommand extends BukkitCommand {
|
||||||
|
public static final List<String> TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste", "verbon", "verboff");
|
||||||
|
|
||||||
|
public TimingsCommand(String name) {
|
||||||
|
super(name);
|
||||||
|
this.description = "Manages Spigot Timings data to see performance of the server.";
|
||||||
|
this.usageMessage = "/timings <reset|report|on|off|verbon|verboff>";
|
||||||
|
this.setPermission("bukkit.command.timings");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(CommandSender sender, String currentAlias, String[] args) {
|
||||||
|
if (!testPermission(sender)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (args.length < 1) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final String arg = args[0];
|
||||||
|
if ("on".equalsIgnoreCase(arg)) {
|
||||||
|
Timings.setTimingsEnabled(true);
|
||||||
|
sender.sendMessage("Enabled Timings & Reset");
|
||||||
|
return true;
|
||||||
|
} else if ("off".equalsIgnoreCase(arg)) {
|
||||||
|
Timings.setTimingsEnabled(false);
|
||||||
|
sender.sendMessage("Disabled Timings");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Timings.isTimingsEnabled()) {
|
||||||
|
sender.sendMessage("Please enable timings by typing /timings on");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ("verbon".equalsIgnoreCase(arg)) {
|
||||||
|
Timings.setVerboseTimingsEnabled(true);
|
||||||
|
sender.sendMessage("Enabled Verbose Timings");
|
||||||
|
return true;
|
||||||
|
} else if ("verboff".equalsIgnoreCase(arg)) {
|
||||||
|
Timings.setVerboseTimingsEnabled(false);
|
||||||
|
sender.sendMessage("Disabled Verbose Timings");
|
||||||
|
return true;
|
||||||
|
} else if ("reset".equalsIgnoreCase(arg)) {
|
||||||
|
TimingsManager.reset();
|
||||||
|
sender.sendMessage("Timings reset");
|
||||||
|
} else if ("cost".equals(arg)) {
|
||||||
|
sender.sendMessage("Timings cost: " + TimingsExport.getCost());
|
||||||
|
} else if (
|
||||||
|
"paste".equalsIgnoreCase(arg) ||
|
||||||
|
"report".equalsIgnoreCase(arg) ||
|
||||||
|
"get".equalsIgnoreCase(arg) ||
|
||||||
|
"merged".equalsIgnoreCase(arg) ||
|
||||||
|
"separate".equalsIgnoreCase(arg)
|
||||||
|
) {
|
||||||
|
TimingsExport.reportTimings(sender);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> tabComplete(CommandSender sender, String alias, String[] args) {
|
||||||
|
Validate.notNull(sender, "Sender cannot be null");
|
||||||
|
Validate.notNull(args, "Arguments cannot be null");
|
||||||
|
Validate.notNull(alias, "Alias cannot be null");
|
||||||
|
|
||||||
|
if (args.length == 1) {
|
||||||
|
return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS,
|
||||||
|
new ArrayList<String>(TIMINGS_SUBCOMMANDS.size()));
|
||||||
|
}
|
||||||
|
return ImmutableList.of();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,373 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
import org.bukkit.command.RemoteConsoleCommandSender;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.MemorySection;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.JSONValue;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.management.GarbageCollectorMXBean;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
|
import java.lang.management.RuntimeMXBean;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
|
import static co.aikar.timings.TimingsManager.HISTORY;
|
||||||
|
import static co.aikar.util.JSONUtil.*;
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
|
||||||
|
class TimingsExport extends Thread {
|
||||||
|
|
||||||
|
private final CommandSender sender;
|
||||||
|
private final Map out;
|
||||||
|
private final TimingHistory[] history;
|
||||||
|
|
||||||
|
TimingsExport(CommandSender sender, Map out, TimingHistory[] history) {
|
||||||
|
super("Timings paste thread");
|
||||||
|
this.sender = sender;
|
||||||
|
this.out = out;
|
||||||
|
this.history = history;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an XML report of the timings to be uploaded for parsing.
|
||||||
|
*
|
||||||
|
* @param sender Who to report to
|
||||||
|
*/
|
||||||
|
static void reportTimings(CommandSender sender) {
|
||||||
|
Map parent = createObject(
|
||||||
|
// Get some basic system details about the server
|
||||||
|
pair("version", Bukkit.getVersion()),
|
||||||
|
pair("maxplayers", Bukkit.getMaxPlayers()),
|
||||||
|
pair("start", TimingsManager.timingStart / 1000),
|
||||||
|
pair("end", System.currentTimeMillis() / 1000),
|
||||||
|
pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000)
|
||||||
|
);
|
||||||
|
if (!TimingsManager.privacy) {
|
||||||
|
appendObjectData(parent,
|
||||||
|
pair("server", Bukkit.getServerName()),
|
||||||
|
pair("motd", Bukkit.getServer().getMotd()),
|
||||||
|
pair("online-mode", Bukkit.getServer().getOnlineMode()),
|
||||||
|
pair("icon", Bukkit.getServer().getServerIcon().getData())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Runtime runtime = Runtime.getRuntime();
|
||||||
|
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
|
||||||
|
|
||||||
|
parent.put("system", createObject(
|
||||||
|
pair("timingcost", getCost()),
|
||||||
|
pair("name", System.getProperty("os.name")),
|
||||||
|
pair("version", System.getProperty("os.version")),
|
||||||
|
pair("jvmversion", System.getProperty("java.version")),
|
||||||
|
pair("arch", System.getProperty("os.arch")),
|
||||||
|
pair("maxmem", runtime.maxMemory()),
|
||||||
|
pair("cpu", runtime.availableProcessors()),
|
||||||
|
pair("runtime", ManagementFactory.getRuntimeMXBean().getUptime()),
|
||||||
|
pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")),
|
||||||
|
pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), new Function<GarbageCollectorMXBean, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(GarbageCollectorMXBean input) {
|
||||||
|
return pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime()));
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<Material> tileEntityTypeSet = Sets.newHashSet();
|
||||||
|
Set<EntityType> entityTypeSet = Sets.newHashSet();
|
||||||
|
|
||||||
|
int size = HISTORY.size();
|
||||||
|
TimingHistory[] history = new TimingHistory[size + 1];
|
||||||
|
int i = 0;
|
||||||
|
for (TimingHistory timingHistory : HISTORY) {
|
||||||
|
tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet);
|
||||||
|
entityTypeSet.addAll(timingHistory.entityTypeSet);
|
||||||
|
history[i++] = timingHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
history[i] = new TimingHistory(); // Current snapshot
|
||||||
|
tileEntityTypeSet.addAll(history[i].tileEntityTypeSet);
|
||||||
|
entityTypeSet.addAll(history[i].entityTypeSet);
|
||||||
|
|
||||||
|
|
||||||
|
Map handlers = createObject();
|
||||||
|
for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) {
|
||||||
|
for (TimingHandler id : group.handlers) {
|
||||||
|
if (!id.timed && !id.isSpecial()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handlers.put(id.id, toArray(
|
||||||
|
group.id,
|
||||||
|
id.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.put("idmap", createObject(
|
||||||
|
pair("groups", toObjectMapper(
|
||||||
|
TimingIdentifier.GROUP_MAP.values(), new Function<TimingIdentifier.TimingGroup, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(TimingIdentifier.TimingGroup group) {
|
||||||
|
return pair(group.id, group.name);
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
pair("handlers", handlers),
|
||||||
|
pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), new Function<Map.Entry<String, Integer>, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Map.Entry<String, Integer> input) {
|
||||||
|
return pair(input.getValue(), input.getKey());
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
pair("tileentity",
|
||||||
|
toObjectMapper(tileEntityTypeSet, new Function<Material, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Material input) {
|
||||||
|
return pair(input.getId(), input.name());
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
pair("entity",
|
||||||
|
toObjectMapper(entityTypeSet, new Function<EntityType, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(EntityType input) {
|
||||||
|
return pair(input.getTypeId(), input.name());
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
));
|
||||||
|
|
||||||
|
// Information about loaded plugins
|
||||||
|
|
||||||
|
parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(),
|
||||||
|
new Function<Plugin, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Plugin plugin) {
|
||||||
|
return pair(plugin.getName(), createObject(
|
||||||
|
pair("version", plugin.getDescription().getVersion()),
|
||||||
|
pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()),
|
||||||
|
pair("website", plugin.getDescription().getWebsite()),
|
||||||
|
pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", "))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Information on the users Config
|
||||||
|
|
||||||
|
parent.put("config", createObject(
|
||||||
|
pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
|
||||||
|
pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
|
||||||
|
pair("paperspigot", mapAsJSON(Bukkit.spigot().getPaperSpigotConfig(), null))
|
||||||
|
));
|
||||||
|
|
||||||
|
new TimingsExport(sender, parent, history).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
static long getCost() {
|
||||||
|
// Benchmark the users System.nanotime() for cost basis
|
||||||
|
int passes = 100;
|
||||||
|
TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1");
|
||||||
|
TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2");
|
||||||
|
TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3");
|
||||||
|
TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4");
|
||||||
|
TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5");
|
||||||
|
TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6");
|
||||||
|
|
||||||
|
long start = System.nanoTime();
|
||||||
|
for (int i = 0; i < passes; i++) {
|
||||||
|
SAMPLER1.startTiming();
|
||||||
|
SAMPLER2.startTiming();
|
||||||
|
SAMPLER3.startTiming();
|
||||||
|
SAMPLER3.stopTiming();
|
||||||
|
SAMPLER4.startTiming();
|
||||||
|
SAMPLER5.startTiming();
|
||||||
|
SAMPLER6.startTiming();
|
||||||
|
SAMPLER6.stopTiming();
|
||||||
|
SAMPLER5.stopTiming();
|
||||||
|
SAMPLER4.stopTiming();
|
||||||
|
SAMPLER2.stopTiming();
|
||||||
|
SAMPLER1.stopTiming();
|
||||||
|
}
|
||||||
|
long timingsCost = (System.nanoTime() - start) / passes / 6;
|
||||||
|
SAMPLER1.reset(true);
|
||||||
|
SAMPLER2.reset(true);
|
||||||
|
SAMPLER3.reset(true);
|
||||||
|
SAMPLER4.reset(true);
|
||||||
|
SAMPLER5.reset(true);
|
||||||
|
SAMPLER6.reset(true);
|
||||||
|
return timingsCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) {
|
||||||
|
|
||||||
|
JSONObject object = new JSONObject();
|
||||||
|
for (String key : config.getKeys(false)) {
|
||||||
|
String fullKey = (parentKey != null ? parentKey + "." + key : key);
|
||||||
|
if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Object val = config.get(key);
|
||||||
|
|
||||||
|
object.put(key, valAsJSON(val, fullKey));
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object valAsJSON(Object val, final String parentKey) {
|
||||||
|
if (!(val instanceof MemorySection)) {
|
||||||
|
if (val instanceof List) {
|
||||||
|
Iterable<Object> v = (Iterable<Object>) val;
|
||||||
|
return toArrayMapper(v, new Function<Object, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(Object input) {
|
||||||
|
return valAsJSON(input, parentKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return val.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return mapAsJSON((ConfigurationSection) val, parentKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CallToThreadRun")
|
||||||
|
@Override
|
||||||
|
public synchronized void start() {
|
||||||
|
if (sender instanceof RemoteConsoleCommandSender) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Warning: Timings report done over RCON will cause lag spikes.");
|
||||||
|
sender.sendMessage(ChatColor.RED + "You should use " + ChatColor.YELLOW +
|
||||||
|
"/timings report" + ChatColor.RED + " in game or console.");
|
||||||
|
run();
|
||||||
|
} else {
|
||||||
|
super.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "Preparing Timings Report...");
|
||||||
|
|
||||||
|
|
||||||
|
out.put("data", toArrayMapper(history, new Function<TimingHistory, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(TimingHistory input) {
|
||||||
|
return input.export();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
String response = null;
|
||||||
|
try {
|
||||||
|
HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection();
|
||||||
|
con.setDoOutput(true);
|
||||||
|
con.setRequestProperty("User-Agent", "Spigot/" + Bukkit.getServerName() + "/" + InetAddress.getLocalHost().getHostName());
|
||||||
|
con.setRequestMethod("POST");
|
||||||
|
con.setInstanceFollowRedirects(false);
|
||||||
|
|
||||||
|
OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{
|
||||||
|
this.def.setLevel(7);
|
||||||
|
}};
|
||||||
|
|
||||||
|
request.write(JSONValue.toJSONString(out).getBytes("UTF-8"));
|
||||||
|
request.close();
|
||||||
|
|
||||||
|
response = getResponse(con);
|
||||||
|
|
||||||
|
if (con.getResponseCode() != 302) {
|
||||||
|
sender.sendMessage(
|
||||||
|
ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage());
|
||||||
|
sender.sendMessage(ChatColor.RED + "Check your logs for more information");
|
||||||
|
if (response != null) {
|
||||||
|
Bukkit.getLogger().log(Level.SEVERE, response);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String location = con.getHeaderField("Location");
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "View Timings Report: " + location);
|
||||||
|
if (!(sender instanceof ConsoleCommandSender)) {
|
||||||
|
Bukkit.getLogger().log(Level.INFO, "View Timings Report: " + location);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null && !response.isEmpty()) {
|
||||||
|
Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
|
||||||
|
if (response != null) {
|
||||||
|
Bukkit.getLogger().log(Level.SEVERE, response);
|
||||||
|
}
|
||||||
|
Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getResponse(HttpURLConnection con) throws IOException {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
is = con.getInputStream();
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
byte[] b = new byte[1024];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = is.read(b)) != -1) {
|
||||||
|
bos.write(b, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return bos.toString();
|
||||||
|
|
||||||
|
} catch (IOException ex) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (is != null) {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.EvictingQueue;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.java.PluginClassLoader;
|
||||||
|
import co.aikar.util.LoadingMap;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public final class TimingsManager {
|
||||||
|
static final Map<TimingIdentifier, TimingHandler> TIMING_MAP =
|
||||||
|
Collections.synchronizedMap(LoadingMap.newHashMap(
|
||||||
|
new Function<TimingIdentifier, TimingHandler>() {
|
||||||
|
@Override
|
||||||
|
public TimingHandler apply(TimingIdentifier id) {
|
||||||
|
return (id.protect ?
|
||||||
|
new UnsafeTimingHandler(id) :
|
||||||
|
new TimingHandler(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
256, .5F
|
||||||
|
));
|
||||||
|
public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler();
|
||||||
|
public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK);
|
||||||
|
public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins");
|
||||||
|
public static List<String> hiddenConfigs = new ArrayList<String>();
|
||||||
|
public static boolean privacy = false;
|
||||||
|
|
||||||
|
static final Collection<TimingHandler> HANDLERS = new ArrayDeque<TimingHandler>();
|
||||||
|
static final ArrayDeque<TimingHistory.MinuteReport> MINUTE_REPORTS = new ArrayDeque<TimingHistory.MinuteReport>();
|
||||||
|
|
||||||
|
static EvictingQueue<TimingHistory> HISTORY = EvictingQueue.create(12);
|
||||||
|
static TimingHandler CURRENT;
|
||||||
|
static long timingStart = 0;
|
||||||
|
static long historyStart = 0;
|
||||||
|
static boolean needsFullReset = false;
|
||||||
|
static boolean needsRecheckEnabled = false;
|
||||||
|
|
||||||
|
private TimingsManager() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all timing data on the next tick
|
||||||
|
*/
|
||||||
|
static void reset() {
|
||||||
|
needsFullReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ticked every tick by CraftBukkit to count the number of times a timer
|
||||||
|
* caused TPS loss.
|
||||||
|
*/
|
||||||
|
static void tick() {
|
||||||
|
if (Timings.timingsEnabled) {
|
||||||
|
boolean violated = FULL_SERVER_TICK.isViolated();
|
||||||
|
|
||||||
|
for (TimingHandler handler : HANDLERS) {
|
||||||
|
if (handler.isSpecial()) {
|
||||||
|
// We manually call this
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handler.processTick(violated);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size();
|
||||||
|
TimingHistory.timedTicks++;
|
||||||
|
// Generate TPS/Ping/Tick reports every minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void stopServer() {
|
||||||
|
Timings.timingsEnabled = false;
|
||||||
|
recheckEnabled();
|
||||||
|
}
|
||||||
|
static void recheckEnabled() {
|
||||||
|
synchronized (TIMING_MAP) {
|
||||||
|
for (TimingHandler timings : TIMING_MAP.values()) {
|
||||||
|
timings.checkEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needsRecheckEnabled = false;
|
||||||
|
}
|
||||||
|
static void resetTimings() {
|
||||||
|
if (needsFullReset) {
|
||||||
|
// Full resets need to re-check every handlers enabled state
|
||||||
|
// Timing map can be modified from async so we must sync on it.
|
||||||
|
synchronized (TIMING_MAP) {
|
||||||
|
for (TimingHandler timings : TIMING_MAP.values()) {
|
||||||
|
timings.reset(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bukkit.getLogger().log(Level.INFO, "Timings Reset");
|
||||||
|
HISTORY.clear();
|
||||||
|
needsFullReset = false;
|
||||||
|
needsRecheckEnabled = false;
|
||||||
|
timingStart = System.currentTimeMillis();
|
||||||
|
} else {
|
||||||
|
// Soft resets only need to act on timings that have done something
|
||||||
|
// Handlers can only be modified on main thread.
|
||||||
|
for (TimingHandler timings : HANDLERS) {
|
||||||
|
timings.reset(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLERS.clear();
|
||||||
|
MINUTE_REPORTS.clear();
|
||||||
|
|
||||||
|
TimingHistory.resetTicks(true);
|
||||||
|
historyStart = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimingHandler getHandler(String group, String name, Timing parent, boolean protect) {
|
||||||
|
return TIMING_MAP.get(new TimingIdentifier(group, name, parent, protect));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Due to access restrictions, we need a helper method to get a Command TimingHandler with String group</p>
|
||||||
|
*
|
||||||
|
* Plugins should never call this
|
||||||
|
*
|
||||||
|
* @param pluginName Plugin this command is associated with
|
||||||
|
* @param command Command to get timings for
|
||||||
|
* @return TimingHandler
|
||||||
|
*/
|
||||||
|
public static Timing getCommandTiming(String pluginName, Command command) {
|
||||||
|
Plugin plugin = null;
|
||||||
|
final Server server = Bukkit.getServer();
|
||||||
|
if (!("minecraft".equals(pluginName) || "bukkit".equals(pluginName) || "Spigot".equals(pluginName) ||
|
||||||
|
server == null)) {
|
||||||
|
plugin = server.getPluginManager().getPlugin(pluginName);
|
||||||
|
if (plugin == null) {
|
||||||
|
// Plugin is passing custom fallback prefix, try to look up by class loader
|
||||||
|
plugin = getPluginByClassloader(command.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (plugin == null) {
|
||||||
|
return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the
|
||||||
|
* Plugin that created this class.
|
||||||
|
*
|
||||||
|
* @param clazz Class to check
|
||||||
|
* @return Plugin if created by a plugin
|
||||||
|
*/
|
||||||
|
public static Plugin getPluginByClassloader(Class<?> clazz) {
|
||||||
|
if (clazz == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final ClassLoader classLoader = clazz.getClassLoader();
|
||||||
|
if (classLoader instanceof PluginClassLoader) {
|
||||||
|
PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;
|
||||||
|
return pluginClassLoader.getPlugin();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
class UnsafeTimingHandler extends TimingHandler {
|
||||||
|
|
||||||
|
UnsafeTimingHandler(TimingIdentifier id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkThread() {
|
||||||
|
if (!Bukkit.isPrimaryThread()) {
|
||||||
|
throw new IllegalStateException("Calling Timings from Async Operation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startTiming() {
|
||||||
|
checkThread();
|
||||||
|
super.startTiming();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopTiming() {
|
||||||
|
checkThread();
|
||||||
|
super.stopTiming();
|
||||||
|
}
|
||||||
|
}
|
123
Atticus/Atticus-API/src/main/java/co/aikar/util/JSONUtil.java
Normal file
123
Atticus/Atticus-API/src/main/java/co/aikar/util/JSONUtil.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package co.aikar.util;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import org.json.simple.JSONArray;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides Utility methods that assist with generating JSON Objects
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
|
||||||
|
public final class JSONUtil {
|
||||||
|
private JSONUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a key/value "JSONPair" object
|
||||||
|
* @param key
|
||||||
|
* @param obj
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONPair pair(String key, Object obj) {
|
||||||
|
return new JSONPair(key, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONPair pair(long key, Object obj) {
|
||||||
|
return new JSONPair(String.valueOf(key), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new JSON object from multiple JsonPair key/value pairs
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Map createObject(JSONPair... data) {
|
||||||
|
return appendObjectData(new LinkedHashMap(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This appends multiple key/value Obj pairs into a JSON Object
|
||||||
|
* @param parent
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Map appendObjectData(Map parent, JSONPair... data) {
|
||||||
|
for (JSONPair JSONPair : data) {
|
||||||
|
parent.put(JSONPair.key, JSONPair.val);
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This builds a JSON array from a set of data
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List toArray(Object... data) {
|
||||||
|
return Lists.newArrayList(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These help build a single JSON array using a mapper function
|
||||||
|
* @param collection
|
||||||
|
* @param mapper
|
||||||
|
* @param <E>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <E> List toArrayMapper(E[] collection, Function<E, Object> mapper) {
|
||||||
|
return toArrayMapper(Lists.newArrayList(collection), mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E> List toArrayMapper(Iterable<E> collection, Function<E, Object> mapper) {
|
||||||
|
List array = Lists.newArrayList();
|
||||||
|
for (E e : collection) {
|
||||||
|
Object object = mapper.apply(e);
|
||||||
|
if (object != null) {
|
||||||
|
array.add(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These help build a single JSON Object from a collection, using a mapper function
|
||||||
|
* @param collection
|
||||||
|
* @param mapper
|
||||||
|
* @param <E>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <E> Map toObjectMapper(E[] collection, Function<E, JSONPair> mapper) {
|
||||||
|
return toObjectMapper(Lists.newArrayList(collection), mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E> Map toObjectMapper(Iterable<E> collection, Function<E, JSONPair> mapper) {
|
||||||
|
Map object = Maps.newLinkedHashMap();
|
||||||
|
for (E e : collection) {
|
||||||
|
JSONPair JSONPair = mapper.apply(e);
|
||||||
|
if (JSONPair != null) {
|
||||||
|
object.put(JSONPair.key, JSONPair.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply stores a key and a value, used internally by many methods below.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PublicInnerClass")
|
||||||
|
public static class JSONPair {
|
||||||
|
final String key;
|
||||||
|
final Object val;
|
||||||
|
|
||||||
|
JSONPair(String key, Object val) {
|
||||||
|
this.key = key;
|
||||||
|
this.val = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user