From 7f6b80a0b75f9adb5df8e59bafcead5134c03326 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 06:09:31 +0000 Subject: [PATCH 1/8] Bump io.javalin:javalin from 6.7.0 to 7.0.1 Bumps [io.javalin:javalin](https://github.com/javalin/javalin) from 6.7.0 to 7.0.1. - [Release notes](https://github.com/javalin/javalin/releases) - [Commits](https://github.com/javalin/javalin/compare/javalin-parent-6.7.0...javalin-parent-7.0.1) --- updated-dependencies: - dependency-name: io.javalin:javalin dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6bd78e15527f5173fb68cac69928730c6907ca4d Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 10:20:40 +0200 Subject: [PATCH 2/8] use `config.routes` in `Javalin.create` to set up the routes --- .../java/pro/cloudnode/smp/smpcore/REST.java | 313 +++++++++--------- 1 file changed, 165 insertions(+), 148 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/REST.java b/src/main/java/pro/cloudnode/smp/smpcore/REST.java index 61b023d..14d2508 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/REST.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/REST.java @@ -16,9 +16,172 @@ import java.util.UUID; public class REST { - final @NotNull Javalin javalin = Javalin.create(config -> config.jsonMapper(new Mapper())); + final @NotNull Javalin javalin; - private void e404 (final @NotNull io.javalin.http.Context ctx) { + public REST(final int port) { + javalin = Javalin.create(config -> { + config.jsonMapper(new Mapper()); + + 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( + "/", ctx -> { + final @NotNull JsonObject obj = new JsonObject(); + obj.addProperty("version", SMPCore.getInstance().getPluginMeta().getVersion()); + obj.addProperty("time", SMPCore.gameTime().getTime()); + ctx.json(obj); + } + ); + + config.routes.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); + } + ); + + config.routes.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); + } + ); + + config.routes.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); + } + ); + + config.routes.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); + } + ); + }).start(port); + } + + private void e404(final @NotNull io.javalin.http.Context ctx) { ctx.status(404); final @NotNull JsonObject obj = new JsonObject(); obj.addProperty("error", "not found"); @@ -59,152 +222,6 @@ private void e404 (final @NotNull io.javalin.http.Context ctx) { 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(); From 9eebaa9fc3507f68451aabbde86f1adb06e0d7b1 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 10:26:07 +0200 Subject: [PATCH 3/8] move REST to API subpackage --- .../java/pro/cloudnode/smp/smpcore/REST.java | 238 ----------------- .../pro/cloudnode/smp/smpcore/SMPCore.java | 5 +- .../pro/cloudnode/smp/smpcore/api/REST.java | 250 ++++++++++++++++++ 3 files changed, 253 insertions(+), 240 deletions(-) delete mode 100644 src/main/java/pro/cloudnode/smp/smpcore/REST.java create mode 100644 src/main/java/pro/cloudnode/smp/smpcore/api/REST.java 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 14d2508..0000000 --- a/src/main/java/pro/cloudnode/smp/smpcore/REST.java +++ /dev/null @@ -1,238 +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; - - public REST(final int port) { - javalin = Javalin.create(config -> { - config.jsonMapper(new Mapper()); - - 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( - "/", ctx -> { - final @NotNull JsonObject obj = new JsonObject(); - obj.addProperty("version", SMPCore.getInstance().getPluginMeta().getVersion()); - obj.addProperty("time", SMPCore.gameTime().getTime()); - ctx.json(obj); - } - ); - - config.routes.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); - } - ); - - config.routes.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); - } - ); - - config.routes.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); - } - ); - - config.routes.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); - } - ); - }).start(port); - } - - 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 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..66f87d4 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java @@ -0,0 +1,250 @@ +package pro.cloudnode.smp.smpcore.api; + +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.config.JavalinConfig; +import io.javalin.json.JsonMapper; +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.Nation; +import pro.cloudnode.smp.smpcore.SMPCore; + +import java.lang.reflect.Type; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +public class REST { + private final @NotNull Javalin javalin; + + public REST(final int port) { + javalin = Javalin.create(config -> { + config.jsonMapper(new Mapper()); + }).start(port); + } + + 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( + "/", ctx -> { + final @NotNull JsonObject obj = new JsonObject(); + obj.addProperty("version", SMPCore.getInstance().getPluginMeta().getVersion()); + obj.addProperty("time", SMPCore.gameTime().getTime()); + ctx.json(obj); + } + ); + + config.routes.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); + } + ); + + config.routes.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); + } + ); + + config.routes.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); + } + ); + + config.routes.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); + } + ); + } + + 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 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); + } + } +} From bb303cba6da11226c7a19aa5c70cfd915812a912 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 11:03:48 +0200 Subject: [PATCH 4/8] move routes to their own classes --- .../pro/cloudnode/smp/smpcore/api/REST.java | 206 ++---------------- .../smp/smpcore/api/routes/Members.java | 132 +++++++++++ .../smp/smpcore/api/routes/Nations.java | 71 ++++++ 3 files changed, 221 insertions(+), 188 deletions(-) create mode 100644 src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java create mode 100644 src/main/java/pro/cloudnode/smp/smpcore/api/routes/Nations.java diff --git a/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java b/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java index 66f87d4..2614d70 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/REST.java @@ -2,23 +2,18 @@ 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.config.JavalinConfig; +import io.javalin.http.Context; import io.javalin.json.JsonMapper; -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.Nation; 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; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; public class REST { private final @NotNull Javalin javalin; @@ -29,6 +24,13 @@ public REST(final int port) { }).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(); } @@ -43,197 +45,25 @@ private void configureRoutes(final @NotNull JavalinConfig config) { ctx.header("Access-Control-Max-Age", "3600"); }); - config.routes.get( - "/", ctx -> { - final @NotNull JsonObject obj = new JsonObject(); - obj.addProperty("version", SMPCore.getInstance().getPluginMeta().getVersion()); - obj.addProperty("time", SMPCore.gameTime().getTime()); - ctx.json(obj); - } - ); - - config.routes.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); - } - ); - - config.routes.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); - } - ); - config.routes.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); - } - ); + config.routes.get("/", REST::info); - config.routes.get( - "/nations/{id}", ctx -> { - final @Nullable String include = ctx.queryParam("include"); + final var members = new Members(this); + config.routes.get("/members", members::list); + config.routes.get("/members/{uuid}", members::get); - 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); - } - ); + final var nations = new Nations(this); + config.routes.get("/nations", nations::list); + config.routes.get("/nations/{id}", nations::get); } - private void e404(final @NotNull io.javalin.http.Context ctx) { + public void e404(final @NotNull 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 static final class Mapper implements JsonMapper { private final @NotNull Gson gson = new GsonBuilder().serializeNulls().create(); 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..effaf54 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java @@ -0,0 +1,132 @@ +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.Nation; +import pro.cloudnode.smp.smpcore.SMPCore; +import pro.cloudnode.smp.smpcore.api.REST; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +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; + } + + public void list(final @NotNull Context 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 = map(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", Nations.map(optionalNation.get())); + } + } + } + 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); + } +} From 47bd6508a468d0151fa1bd5f36b2345d54b876bd Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 12:02:48 +0200 Subject: [PATCH 5/8] use enhanced switch --- .../pro/cloudnode/smp/smpcore/api/routes/Members.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 index effaf54..453a9c9 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java @@ -78,15 +78,18 @@ public void list(final @NotNull Context ctx) { for (final @NotNull Member member : members) { if (filter != null) switch (filter) { - case "online": + case "online" -> { if (member.staff || !member.player().isOnline()) - continue; - case "offline": + continue; + } + case "offline" -> { if (!member.staff && member.player().isOnline()) continue; - case "banned": + } + case "banned" -> { if (!member.player().isBanned()) continue; + } } final @NotNull JsonObject m = map(member); if (include != null) { From 9d7bc07235a08a07a551c92f0d51a53f945a7849 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 13:26:51 +0200 Subject: [PATCH 6/8] reduce NPath complexity --- .../smp/smpcore/api/routes/Members.java | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) 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 index 453a9c9..b65726e 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java @@ -13,8 +13,10 @@ 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; @@ -41,56 +43,59 @@ public Members(final @NotNull REST rest) { return obj; } - public void list(final @NotNull Context 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; + 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(); } + } - 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; + 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) { + if (filter == null) + return _ -> true; - final @NotNull Set<@NotNull Member> members = limit == null ? Member.get() : Member.get(limit, page); + 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(); + default -> _ -> true; + }; + } + + public void list(final @NotNull Context ctx) { + final @Nullable String include = ctx.queryParam("include"); + + 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")); + 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; - } - } + if (!filterPredicate.test(member)) + continue; + final @NotNull JsonObject m = map(member); if (include != null) { switch (include) { From 1ad9419982fab5e4fc2bfe129aca3a99da31dd09 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 13:27:35 +0200 Subject: [PATCH 7/8] add null case and reform switch --- .../cloudnode/smp/smpcore/api/routes/Members.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) 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 index b65726e..f077668 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java @@ -66,17 +66,11 @@ private static int parseInt(final @Nullable String value, final int defaultValue } private static Predicate resolveFilter(final @Nullable String filter) { - if (filter == null) - return _ -> true; - 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(); - default -> _ -> true; + 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; }; } From 19d6c2eb7cec86ecdfd36c5f553aa64d41294db3 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 3 Mar 2026 13:30:25 +0200 Subject: [PATCH 8/8] further reduce NPath complexity of Members.list method --- .../smp/smpcore/api/routes/Members.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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 index f077668..e90261a 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/api/routes/Members.java @@ -8,7 +8,6 @@ import org.jetbrains.annotations.Nullable; import pro.cloudnode.smp.smpcore.CachedProfile; 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; @@ -74,9 +73,18 @@ private static Predicate resolveFilter(final @Nullable String filter) { }; } - public void list(final @NotNull Context ctx) { - final @Nullable String include = ctx.queryParam("include"); + 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); @@ -86,22 +94,14 @@ public void list(final @NotNull Context ctx) { 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); - 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", Nations.map(optionalNation.get())); - } - } - } + applyInclude(m, member, include); arr.add(m); } ctx.json(arr);