basically all curtis code from 5 years ago

This commit is contained in:
Brandon 2023-09-04 17:01:05 +01:00
parent c103e3644f
commit 9abe945f21
8111 changed files with 679111 additions and 0 deletions

18
API/.gitignore vendored Normal file
View 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
View 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>

View 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);
}
}

View 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);
}
}

View 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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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();
}

View 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);
}
}

View 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();
}
}

View 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;
}
}

View File

@ -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);
}
}

View 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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<>();
}

View File

@ -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();
}
}

View 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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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())
));
}
}

View File

@ -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);
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View 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;
}
}
}

View 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;
}
}

View 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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View 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();
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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";
}
}

View 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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View 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";
}
}
}

View 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);
}
}

View 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();
}
}

View 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);
}

View 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;
});
}
}

View 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();
}

View 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();
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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())
);
});
}
}

View File

@ -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;
});
}
}

View File

@ -0,0 +1,7 @@
package org.hcrival.api.util;
public class ChatColor {
}

View 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();
}
}

View 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;
}
}

View 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(", ");
}

View 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);
}
}

View 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();
}
}

View File

@ -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);
}

View 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;
}
}
}

View File

@ -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 {
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
package org.hcrival.api.util.exception;
public class DataNotFoundException extends Exception {
}

View File

@ -0,0 +1 @@
spring.mvc.converters.preferred-json-mapper=gson

5
Atticus/.gitignore vendored Normal file
View 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
View 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

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View 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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View 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