diff --git a/pom.xml b/pom.xml
index 38a79f4..e11ffd4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,7 @@
io.javalin
javalin
- 6.7.0
+ 7.0.1
provided
diff --git a/src/main/java/pro/cloudnode/smp/smpcore/REST.java b/src/main/java/pro/cloudnode/smp/smpcore/REST.java
deleted file mode 100644
index 61b023d..0000000
--- a/src/main/java/pro/cloudnode/smp/smpcore/REST.java
+++ /dev/null
@@ -1,221 +0,0 @@
-package pro.cloudnode.smp.smpcore;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import io.javalin.Javalin;
-import io.javalin.json.JsonMapper;
-import org.bukkit.OfflinePlayer;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.lang.reflect.Type;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
-
-public class REST {
- final @NotNull Javalin javalin = Javalin.create(config -> config.jsonMapper(new Mapper()));
-
- private void e404 (final @NotNull io.javalin.http.Context ctx) {
- ctx.status(404);
- final @NotNull JsonObject obj = new JsonObject();
- obj.addProperty("error", "not found");
- ctx.json(obj);
- }
-
- private @NotNull JsonObject getMemberObject(final @NotNull Member member) {
- final @NotNull JsonObject obj = new JsonObject();
- final @NotNull OfflinePlayer player = member.player();
- obj.addProperty("uuid", member.uuid.toString());
- obj.addProperty("name", CachedProfile.getName(player));
- obj.addProperty("nation", member.nationID);
- obj.addProperty("staff", member.staff);
- obj.addProperty("online", !member.staff && player.isOnline());
- obj.addProperty("whitelisted", player.isWhitelisted());
- obj.addProperty("banned", player.isBanned());
- obj.addProperty("altOwner", member.altOwnerUUID == null ? null : member.altOwnerUUID.toString());
- obj.addProperty("added", member.added.getTime());
- obj.addProperty("lastSeen", member.staff ? 0 : player.getLastSeen());
- obj.addProperty("firstSeen", player.getFirstPlayed());
- obj.addProperty("active", member.isActive());
- return obj;
- }
-
- private @NotNull JsonObject getNationObject(final @NotNull Nation nation) {
- final @NotNull JsonObject obj = new JsonObject();
- obj.addProperty("id", nation.id);
- obj.addProperty("name", nation.name);
- obj.addProperty("shortName", nation.shortName);
- obj.addProperty("color", nation.color);
- obj.addProperty("leader", nation.leaderUUID.toString());
- obj.addProperty("viceLeader", nation.viceLeaderUUID.toString());
- obj.addProperty("members", nation.citizens().size());
- obj.addProperty("founded", nation.founded.getTime());
- obj.addProperty("foundedGameTicks", nation.foundedTicks);
- obj.addProperty("foundedGameDate", SMPCore.gameTime(nation.foundedTicks).getTime());
- obj.addProperty("bank", nation.bank);
- return obj;
- }
-
- public REST(final int port) {
- javalin.before(ctx -> {
- final @Nullable String origin = ctx.header("Origin");
- ctx.header("Access-Control-Allow-Origin", origin == null ? "*" : origin);
- ctx.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
- ctx.header("Access-Control-Allow-Headers", "*");
- ctx.header("Access-Control-Allow-Credentials", "true");
- ctx.header("Access-Control-Max-Age", "3600");
- });
-
- javalin.get("/", ctx -> {
- final @NotNull JsonObject obj = new JsonObject();
- obj.addProperty("version", SMPCore.getInstance().getPluginMeta().getVersion());
- obj.addProperty("time", SMPCore.gameTime().getTime());
- ctx.json(obj);
- });
-
- javalin.get("/members", ctx -> {
- final @Nullable String filter = ctx.queryParam("filter");
- final @Nullable String limitString = ctx.queryParam("limit");
- final @Nullable String pageString = ctx.queryParam("page");
- final @Nullable String include = ctx.queryParam("include");
-
- final @Nullable Integer limit;
- if (limitString == null) limit = null;
- else {
- @Nullable Integer t = null;
- try {
- t = Integer.parseInt(limitString);
- }
- catch (final @NotNull NumberFormatException ignored) {}
- limit = t;
- }
-
- final int page;
- if (pageString == null) page = 1;
- else {
- int t;
- try {
- t = Integer.parseInt(pageString);
- }
- catch (final @NotNull NumberFormatException ignored) {
- t = 1;
- }
- page = t;
- }
-
- final @NotNull Set<@NotNull Member> members = limit == null ? Member.get() : Member.get(limit, page);
- final @NotNull JsonArray arr = new JsonArray();
- for (final @NotNull Member member : members) {
- if (filter != null)
- switch (filter) {
- case "online":
- if (member.staff || !member.player().isOnline())
- continue;
- case "offline":
- if (!member.staff && member.player().isOnline())
- continue;
- case "banned":
- if (!member.player().isBanned())
- continue;
- }
- final @NotNull JsonObject m = getMemberObject(member);
- if (include != null) {
- switch (include) {
- case "nation" -> {
- final @NotNull Optional<@NotNull Nation> optionalNation = member.nation();
- if (optionalNation.isEmpty()) m.add("nation", null);
- else m.add("nation", getNationObject(optionalNation.get()));
- }
- }
- }
- arr.add(m);
- }
- ctx.json(arr);
- });
-
- javalin.get("/members/{uuid}", ctx -> {
- final @NotNull UUID uuid;
- try {
- uuid = UUID.fromString(ctx.pathParam("uuid"));
- }
- catch (final @NotNull IllegalArgumentException e) {
- e404(ctx);
- return;
- }
- final @NotNull OfflinePlayer offlinePlayer = SMPCore.getInstance().getServer()
- .getOfflinePlayer(uuid);
- final @NotNull Optional<@NotNull Member> member = Member.get(offlinePlayer);
- if (member.isEmpty()) {
- e404(ctx);
- return;
- }
- final @NotNull Set<@NotNull Member> alts = member.get().getAlts();
- final @NotNull JsonObject obj = getMemberObject(member.get());
- final @NotNull JsonArray altsArray = new JsonArray();
- for (final @NotNull Member alt : alts) {
- final @NotNull JsonObject altObj = new JsonObject();
- final @NotNull OfflinePlayer player = alt.player();
- altObj.addProperty("uuid", alt.uuid.toString());
- altObj.addProperty("name", CachedProfile.getName(player));
- altObj.addProperty("nation", alt.nationID);
- altObj.addProperty("added", alt.added.getTime());
- altObj.addProperty("lastSeen", alt.staff ? 0 : player.getLastSeen());
- altsArray.add(altObj);
- }
- obj.add("alts", altsArray);
- ctx.json(obj);
- });
-
- javalin.get("/nations", ctx -> {
- final @NotNull Set<@NotNull Nation> nations = Nation.get();
- final @NotNull JsonArray arr = new JsonArray();
- for (final @NotNull Nation nation : nations)
- arr.add(getNationObject(nation));
- ctx.json(arr);
- });
-
- javalin.get("/nations/{id}", ctx -> {
- final @Nullable String include = ctx.queryParam("include");
-
- final @NotNull Optional<@NotNull Nation> nation = Nation.get(ctx.pathParam("id"));
- if (nation.isEmpty()) {
- e404(ctx);
- return;
- }
- final @NotNull JsonObject obj = getNationObject(nation.get());
-
- if (include != null) {
- switch (include) {
- case "members" -> {
- final @NotNull JsonArray arr = new JsonArray();
- final @NotNull Set<@NotNull Member> members = nation.get().citizens();
- for (final @NotNull Member member : members)
- arr.add(getMemberObject(member));
- obj.add("members", arr);
- }
- }
- }
-
- ctx.json(obj);
- });
-
- javalin.start(port);
- }
-
- public static final class Mapper implements JsonMapper {
- private final @NotNull Gson gson = new GsonBuilder().serializeNulls().create();
-
- @Override
- public @NotNull String toJsonString(final @NotNull Object obj, final @NotNull Type type) {
- return gson.toJson(obj, type);
- }
-
- @Override
- public @NotNull T fromJsonString(final @NotNull String json, final @NotNull Type targetType) {
- return gson.fromJson(json, targetType);
- }
- }
-}
diff --git a/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java b/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java
index a6fc47c..651a718 100644
--- a/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java
+++ b/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java
@@ -9,6 +9,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import pro.cloudnode.smp.smpcore.api.REST;
import pro.cloudnode.smp.smpcore.command.AltsCommand;
import pro.cloudnode.smp.smpcore.command.BanCommand;
import pro.cloudnode.smp.smpcore.command.CitizensCommand;
@@ -112,7 +113,7 @@ public void onDisable() {
getLogger().log(Level.SEVERE, "failed to close db connection", e);
}
db.close();
- if (rest != null) rest.javalin.stop();
+ if (rest != null) rest.stop();
}
public void reload() {
@@ -121,7 +122,7 @@ public void reload() {
if (messages != null) messages.reload();
setupDatabase();
Member.createStaffTeam();
- if (rest != null) rest.javalin.stop();
+ if (rest != null) rest.stop();
rest = new REST(config.apiPort());
}
diff --git a/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java b/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java
new file mode 100644
index 0000000..2614d70
--- /dev/null
+++ b/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java
@@ -0,0 +1,80 @@
+package pro.cloudnode.smp.smpcore.api;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import io.javalin.Javalin;
+import io.javalin.config.JavalinConfig;
+import io.javalin.http.Context;
+import io.javalin.json.JsonMapper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import pro.cloudnode.smp.smpcore.SMPCore;
+import pro.cloudnode.smp.smpcore.api.routes.Members;
+import pro.cloudnode.smp.smpcore.api.routes.Nations;
+
+import java.lang.reflect.Type;
+
+public class REST {
+ private final @NotNull Javalin javalin;
+
+ public REST(final int port) {
+ javalin = Javalin.create(config -> {
+ config.jsonMapper(new Mapper());
+ }).start(port);
+ }
+
+ private static void info(final @NotNull Context ctx) {
+ final @NotNull JsonObject obj = new JsonObject();
+ obj.addProperty("version", SMPCore.getInstance().getPluginMeta().getVersion());
+ obj.addProperty("time", SMPCore.gameTime().getTime());
+ ctx.json(obj);
+ }
+
+ public void stop() {
+ javalin.stop();
+ }
+
+ private void configureRoutes(final @NotNull JavalinConfig config) {
+ config.routes.before(ctx -> {
+ final @Nullable String origin = ctx.header("Origin");
+ ctx.header("Access-Control-Allow-Origin", origin == null ? "*" : origin);
+ ctx.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
+ ctx.header("Access-Control-Allow-Headers", "*");
+ ctx.header("Access-Control-Allow-Credentials", "true");
+ ctx.header("Access-Control-Max-Age", "3600");
+ });
+
+
+ config.routes.get("/", REST::info);
+
+ final var members = new Members(this);
+ config.routes.get("/members", members::list);
+ config.routes.get("/members/{uuid}", members::get);
+
+ final var nations = new Nations(this);
+ config.routes.get("/nations", nations::list);
+ config.routes.get("/nations/{id}", nations::get);
+ }
+
+ public void e404(final @NotNull Context ctx) {
+ ctx.status(404);
+ final @NotNull JsonObject obj = new JsonObject();
+ obj.addProperty("error", "not found");
+ ctx.json(obj);
+ }
+
+ public static final class Mapper implements JsonMapper {
+ private final @NotNull Gson gson = new GsonBuilder().serializeNulls().create();
+
+ @Override
+ public @NotNull String toJsonString(final @NotNull Object obj, final @NotNull Type type) {
+ return gson.toJson(obj, type);
+ }
+
+ @Override
+ public @NotNull T fromJsonString(final @NotNull String json, final @NotNull Type targetType) {
+ return gson.fromJson(json, targetType);
+ }
+ }
+}
diff --git a/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java
new file mode 100644
index 0000000..e90261a
--- /dev/null
+++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java
@@ -0,0 +1,134 @@
+package pro.cloudnode.smp.smpcore.api.routes;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.javalin.http.Context;
+import org.bukkit.OfflinePlayer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import pro.cloudnode.smp.smpcore.CachedProfile;
+import pro.cloudnode.smp.smpcore.Member;
+import pro.cloudnode.smp.smpcore.SMPCore;
+import pro.cloudnode.smp.smpcore.api.REST;
+
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Predicate;
+
+public final class Members {
+ private final @NotNull REST rest;
+
+ public Members(final @NotNull REST rest) {
+ this.rest = rest;
+ }
+
+ public static @NotNull JsonObject map(final @NotNull Member member) {
+ final @NotNull JsonObject obj = new JsonObject();
+ final @NotNull OfflinePlayer player = member.player();
+ obj.addProperty("uuid", member.uuid.toString());
+ obj.addProperty("name", CachedProfile.getName(player));
+ obj.addProperty("nation", member.nationID);
+ obj.addProperty("staff", member.staff);
+ obj.addProperty("online", !member.staff && player.isOnline());
+ obj.addProperty("whitelisted", player.isWhitelisted());
+ obj.addProperty("banned", player.isBanned());
+ obj.addProperty("altOwner", member.altOwnerUUID == null ? null : member.altOwnerUUID.toString());
+ obj.addProperty("added", member.added.getTime());
+ obj.addProperty("lastSeen", member.staff ? 0 : player.getLastSeen());
+ obj.addProperty("firstSeen", player.getFirstPlayed());
+ obj.addProperty("active", member.isActive());
+ return obj;
+ }
+
+ private static OptionalInt parseInt(final @Nullable String value) {
+ if (value == null)
+ return OptionalInt.empty();
+ try {
+ return OptionalInt.of(Integer.parseInt(value));
+ }
+ catch (final NumberFormatException ignored) {
+ return OptionalInt.empty();
+ }
+ }
+
+ private static int parseInt(final @Nullable String value, final int defaultValue) {
+ if (value == null)
+ return defaultValue;
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (final NumberFormatException ignored) {
+ return defaultValue;
+ }
+ }
+
+ private static Predicate resolveFilter(final @Nullable String filter) {
+ return switch (filter) {
+ case "online" -> member -> !member.staff && member.player().isOnline();
+ case "offline" -> member -> member.staff || !member.player().isOnline();
+ case "banned" -> member -> member.player().isBanned();
+ case null, default -> _ -> true;
+ };
+ }
+
+ private static void applyInclude(@NotNull JsonObject json, @NotNull Member member, @Nullable String include) {
+ if (include == null)
+ return;
+
+ if ("nation".equals(include)) {
+ member.nation()
+ .map(Nations::map)
+ .ifPresentOrElse(nation -> json.add("nation", nation), () -> json.add("nation", null));
+ }
+ }
+
+ public void list(final @NotNull Context ctx) {
+ final var limit = parseInt(ctx.queryParam("limit"));
+ final var page = parseInt(ctx.queryParam("page"), 1);
+
+ final @NotNull Set<@NotNull Member> members = limit.isEmpty() ? Member.get()
+ : Member.get(limit.getAsInt(), page);
+ final @NotNull JsonArray arr = new JsonArray();
+
+ final var filterPredicate = resolveFilter(ctx.queryParam("filter"));
+
+ final @Nullable String include = ctx.queryParam("include");
+
+ for (final @NotNull Member member : members) {
+ if (!filterPredicate.test(member))
+ continue;
+
+ final @NotNull JsonObject m = map(member);
+ applyInclude(m, member, include);
+ arr.add(m);
+ }
+ ctx.json(arr);
+ }
+
+ public void get(final @NotNull Context ctx) {
+ final @NotNull UUID uuid;
+ try {
+ uuid = UUID.fromString(ctx.pathParam("uuid"));
+ }
+ catch (final @NotNull IllegalArgumentException e) {
+ rest.e404(ctx);
+ return;
+ }
+ final @NotNull OfflinePlayer offlinePlayer = SMPCore.getInstance().getServer().getOfflinePlayer(uuid);
+ final @NotNull Optional<@NotNull Member> member = Member.get(offlinePlayer);
+ if (member.isEmpty()) {
+ rest.e404(ctx);
+ return;
+ }
+ final @NotNull Set<@NotNull Member> alts = member.get().getAlts();
+ final @NotNull JsonObject obj = map(member.get());
+ final @NotNull JsonArray altsArray = new JsonArray();
+ for (final @NotNull Member alt : alts) {
+ altsArray.add(map(alt));
+ }
+ obj.add("alts", altsArray);
+ ctx.json(obj);
+ }
+}
diff --git a/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Nations.java b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Nations.java
new file mode 100644
index 0000000..f682f37
--- /dev/null
+++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Nations.java
@@ -0,0 +1,71 @@
+package pro.cloudnode.smp.smpcore.api.routes;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.javalin.http.Context;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import pro.cloudnode.smp.smpcore.Member;
+import pro.cloudnode.smp.smpcore.Nation;
+import pro.cloudnode.smp.smpcore.SMPCore;
+import pro.cloudnode.smp.smpcore.api.REST;
+
+import java.util.Optional;
+import java.util.Set;
+
+public final class Nations {
+ private final @NotNull REST rest;
+
+ public Nations(final @NotNull REST rest) {
+ this.rest = rest;
+ }
+
+ public static @NotNull JsonObject map(final @NotNull Nation nation) {
+ final @NotNull JsonObject obj = new JsonObject();
+ obj.addProperty("id", nation.id);
+ obj.addProperty("name", nation.name);
+ obj.addProperty("shortName", nation.shortName);
+ obj.addProperty("color", nation.color);
+ obj.addProperty("leader", nation.leaderUUID.toString());
+ obj.addProperty("viceLeader", nation.viceLeaderUUID.toString());
+ obj.addProperty("members", nation.citizens().size());
+ obj.addProperty("founded", nation.founded.getTime());
+ obj.addProperty("foundedGameTicks", nation.foundedTicks);
+ obj.addProperty("foundedGameDate", SMPCore.gameTime(nation.foundedTicks).getTime());
+ obj.addProperty("bank", nation.bank);
+ return obj;
+ }
+
+ public void list(final @NotNull Context ctx) {
+ final @NotNull Set<@NotNull Nation> nations = Nation.get();
+ final @NotNull JsonArray arr = new JsonArray();
+ for (final @NotNull Nation nation : nations)
+ arr.add(map(nation));
+ ctx.json(arr);
+ }
+
+ public void get(final @NotNull Context ctx) {
+ final @Nullable String include = ctx.queryParam("include");
+
+ final @NotNull Optional<@NotNull Nation> nation = Nation.get(ctx.pathParam("id"));
+ if (nation.isEmpty()) {
+ rest.e404(ctx);
+ return;
+ }
+ final @NotNull JsonObject obj = map(nation.get());
+
+ if (include != null) {
+ switch (include) {
+ case "members" -> {
+ final @NotNull JsonArray arr = new JsonArray();
+ final @NotNull Set<@NotNull Member> citizens = nation.get().citizens();
+ for (final @NotNull Member member : citizens)
+ arr.add(Members.map(member));
+ obj.add("members", arr);
+ }
+ }
+ }
+
+ ctx.json(obj);
+ }
+}