diff --git a/pom.xml b/pom.xml index ed402a91..62f6ecfb 100644 --- a/pom.xml +++ b/pom.xml @@ -46,12 +46,12 @@ io.javalin javalin - 3.13.12 + 4.1.1 io.javalin javalin-openapi - 3.13.12 + 4.1.1 diff --git a/src/main/java/io/servertap/PluginEntrypoint.java b/src/main/java/io/servertap/PluginEntrypoint.java index 421fb6c0..dc9b1448 100644 --- a/src/main/java/io/servertap/PluginEntrypoint.java +++ b/src/main/java/io/servertap/PluginEntrypoint.java @@ -11,6 +11,7 @@ import io.servertap.api.v1.models.ConsoleLine; import io.servertap.api.v1.websockets.ConsoleListener; import io.servertap.api.v1.websockets.WebsocketHandler; +import io.servertap.utils.GsonJsonMapper; import io.swagger.v3.oas.models.info.Info; import net.milkbowl.vault.economy.Economy; import org.apache.logging.log4j.LogManager; @@ -92,6 +93,8 @@ public void onEnable() { if (app == null) { app = Javalin.create(config -> { + config.jsonMapper(new GsonJsonMapper()); + config.defaultContentType = "application/json"; config.showJavalinBanner = false; @@ -191,11 +194,11 @@ public void onEnable() { get("worlds", ServerApi::worldsGet); post("worlds/save", ServerApi::saveAllWorlds); get("worlds/download", ServerApi::downloadWorlds); - get("worlds/:uuid", ServerApi::worldGet); - get("worlds/:uuid/download", ServerApi::downloadWorld); - post("worlds/:uuid/save", ServerApi::saveWorld); + get("worlds/{uuid}", ServerApi::worldGet); + post("worlds/{uuid}/save", ServerApi::saveWorld); + get("worlds/{uuid}/download", ServerApi::downloadWorld); get("scoreboard", ServerApi::scoreboardGet); - get("scoreboard/:name", ServerApi::objectiveGet); + get("scoreboard/{name}", ServerApi::objectiveGet); // Chat post("chat/broadcast", ServerApi::broadcastPost); @@ -204,8 +207,8 @@ public void onEnable() { // Player routes get("players", PlayerApi::playersGet); get("players/all", PlayerApi::offlinePlayersGet); - get("players/:uuid", PlayerApi::playerGet); - get("players/:playerUuid/:worldUuid/inventory", PlayerApi::getPlayerInv); + get("players/{uuid}", PlayerApi::playerGet); + get("players/{playerUuid}/{worldUuid}/inventory", PlayerApi::getPlayerInv); // Economy routes post("economy/pay", EconomyApi::playerPay); @@ -268,6 +271,7 @@ private OpenApiOptions getOpenApiOptions() { .description(this.getDescription().getDescription()); return new OpenApiOptions(applicationInfo) .path("/swagger-docs") + .includePath(Constants.API_V1 + "/*") .activateAnnotationScanningFor("io.servertap.api.v1") .swagger(new SwaggerOptions("/swagger")); } diff --git a/src/main/java/io/servertap/WebhookEventListener.java b/src/main/java/io/servertap/WebhookEventListener.java index 3d6e6fa2..fcf5e129 100644 --- a/src/main/java/io/servertap/WebhookEventListener.java +++ b/src/main/java/io/servertap/WebhookEventListener.java @@ -4,8 +4,8 @@ import io.servertap.api.v1.models.ItemStack; import io.servertap.api.v1.models.Player; import io.servertap.api.v1.models.events.*; +import io.servertap.utils.GsonSingleton; import net.md_5.bungee.api.ChatColor; - import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.EventHandler; @@ -27,7 +27,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Logger; -import java.util.regex.*; public class WebhookEventListener implements Listener { private List registeredWebhooks; @@ -260,7 +259,7 @@ public PostRequestTask(WebhookEvent webhookEvent, String listener) { @Override public void run() { try { - Gson gson = new Gson(); + Gson gson = GsonSingleton.getInstance(); String jsonContent = gson.toJson(webhookEvent); byte[] output = jsonContent.getBytes(StandardCharsets.UTF_8); diff --git a/src/main/java/io/servertap/api/v1/PlayerApi.java b/src/main/java/io/servertap/api/v1/PlayerApi.java index ae03e8f6..b6f8d28e 100644 --- a/src/main/java/io/servertap/api/v1/PlayerApi.java +++ b/src/main/java/io/servertap/api/v1/PlayerApi.java @@ -42,7 +42,7 @@ public static void playersGet(Context ctx) { } @OpenApi( - path = "/v1/players/:uuid", + path = "/v1/players/{uuid}", method = HttpMethod.GET, summary = "Gets a specific online player by their UUID", tags = {"Player"}, @@ -158,7 +158,7 @@ public static void offlinePlayersGet(Context ctx) { } @OpenApi( - path = "/v1/players/:playerUuid/:worldUuid/inventory", + path = "/v1/players/{playerUuid}/{worldUuid}/inventory", method = HttpMethod.GET, summary = "Gets a specific online player's Inventory in the specified world", tags = {"Player"}, diff --git a/src/main/java/io/servertap/api/v1/ServerApi.java b/src/main/java/io/servertap/api/v1/ServerApi.java index 94e5b2e0..339487c2 100644 --- a/src/main/java/io/servertap/api/v1/ServerApi.java +++ b/src/main/java/io/servertap/api/v1/ServerApi.java @@ -2,7 +2,6 @@ import com.google.gson.Gson; import io.javalin.http.*; -import io.javalin.plugin.json.JavalinJson; import io.javalin.plugin.openapi.annotations.*; import io.servertap.Constants; import io.servertap.Lag; @@ -11,6 +10,7 @@ import io.servertap.api.v1.models.*; import io.servertap.mojang.api.MojangApiService; import io.servertap.mojang.api.models.NameChange; +import io.servertap.utils.GsonSingleton; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.bukkit.BanList; @@ -19,7 +19,10 @@ import org.bukkit.plugin.Plugin; import org.bukkit.scoreboard.ScoreboardManager; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.math.BigDecimal; import java.nio.file.Paths; @@ -160,7 +163,7 @@ public static void saveAllWorlds(Context ctx) { } @OpenApi( - path = "/v1/worlds/:uuid/save", + path = "/v1/worlds/{uuid}/save", summary = "Triggers a world save", method = HttpMethod.POST, tags = {"Server"}, @@ -233,7 +236,7 @@ private static void addFolderToZip(File folder, ZipOutputStream zip, String base } @OpenApi( - path = "/v1/worlds/:uuid/download", + path = "/v1/worlds/{uuid}/download", summary = "Downloads a ZIP compressed archive of the world's folder", method = HttpMethod.GET, tags = {"Server"}, @@ -401,7 +404,7 @@ public static void scoreboardGet(Context ctx) { } @OpenApi( - path = "/v1/scoreboard/:name", + path = "/v1/scoreboard/{name}", summary = "Get information about a specific objective", tags = {"Server"}, headers = { @@ -415,7 +418,7 @@ public static void scoreboardGet(Context ctx) { } ) public static void objectiveGet(Context ctx) { - String objectiveName = ctx.pathParam(":name"); + String objectiveName = ctx.pathParam("name"); ScoreboardManager manager = Bukkit.getScoreboardManager(); org.bukkit.scoreboard.Scoreboard gameScoreboard = manager.getMainScoreboard(); org.bukkit.scoreboard.Objective objective = gameScoreboard.getObjective(objectiveName); @@ -477,7 +480,7 @@ public static void worldsGet(Context ctx) { } @OpenApi( - path = "/v1/worlds/:uuid", + path = "/v1/worlds/{uuid}", summary = "Get information about a specific world", tags = {"Server"}, headers = { @@ -650,7 +653,7 @@ public static void whitelistPost(Context ctx) { } } whitelist.add(newEntry); - final String json = new Gson().toJson(whitelist); + final String json = GsonSingleton.getInstance().toJson(whitelist); try { final String path = Paths.get(directory.getAbsolutePath(), "whitelist.json").toString(); final File myObj = new File(path); @@ -827,11 +830,13 @@ public static void postCommand(Context ctx) { AtomicLong time = new AtomicLong(timeRaw != null ? Long.parseLong(timeRaw) : 0); if (time.get() < 0) time.set(0); - ctx.result(CompletableFuture.supplyAsync(() -> { + ctx.future(CompletableFuture.supplyAsync(() -> { CompletableFuture future = new ServerExecCommandSender().executeCommand(command, time.get(), TimeUnit.MILLISECONDS); try { String output = future.get(); - return "application/json".equalsIgnoreCase(ctx.contentType()) ? JavalinJson.toJson(output) : output; + Gson g = GsonSingleton.getInstance(); + + return "application/json".equalsIgnoreCase(ctx.contentType()) ? g.toJson(output) : output; } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/main/java/io/servertap/api/v1/websockets/WebsocketHandler.java b/src/main/java/io/servertap/api/v1/websockets/WebsocketHandler.java index 834b2175..d96cea44 100644 --- a/src/main/java/io/servertap/api/v1/websockets/WebsocketHandler.java +++ b/src/main/java/io/servertap/api/v1/websockets/WebsocketHandler.java @@ -1,7 +1,7 @@ package io.servertap.api.v1.websockets; +import io.javalin.websocket.WsConfig; import io.javalin.websocket.WsContext; -import io.javalin.websocket.WsHandler; import io.servertap.PluginEntrypoint; import io.servertap.api.v1.models.ConsoleLine; import org.bukkit.Bukkit; @@ -15,7 +15,7 @@ public class WebsocketHandler { private final static Map subscribers = new ConcurrentHashMap<>(); - public static void events(WsHandler ws) { + public static void events(WsConfig ws) { ws.onConnect(ctx -> { subscribers.put(clientHash(ctx), ctx); diff --git a/src/main/java/io/servertap/mojang/api/MojangApiService.java b/src/main/java/io/servertap/mojang/api/MojangApiService.java index 74d9eb9f..38f2546d 100644 --- a/src/main/java/io/servertap/mojang/api/MojangApiService.java +++ b/src/main/java/io/servertap/mojang/api/MojangApiService.java @@ -4,6 +4,7 @@ import com.google.gson.reflect.TypeToken; import io.servertap.mojang.api.models.NameChange; import io.servertap.mojang.api.models.PlayerInfo; +import io.servertap.utils.GsonSingleton; import java.io.BufferedReader; import java.io.IOException; @@ -21,7 +22,7 @@ public class MojangApiService { private static final String getNameHistoryResource = "https://api.mojang.com/user/profiles/%s/names"; public static String getUuid(String username) throws IOException { - Gson gson = new Gson(); + Gson gson = GsonSingleton.getInstance(); ApiResponse apiResponse = getApiResponse(String.format(getUuidResource, username)); if (apiResponse.getHttpStatus() == HttpURLConnection.HTTP_NO_CONTENT) { @@ -34,7 +35,7 @@ public static String getUuid(String username) throws IOException { public static List getNameHistory(String uuid) throws IOException { Type listType = new TypeToken>() { }.getType(); - Gson gson = new Gson(); + Gson gson = GsonSingleton.getInstance(); //This API call doesn't accept UUIDS with dashes ApiResponse apiResponse = getApiResponse(String.format(getNameHistoryResource, uuid).replace("-", "")); diff --git a/src/main/java/io/servertap/utils/GsonJsonMapper.java b/src/main/java/io/servertap/utils/GsonJsonMapper.java new file mode 100644 index 00000000..51ac42ce --- /dev/null +++ b/src/main/java/io/servertap/utils/GsonJsonMapper.java @@ -0,0 +1,11 @@ +package io.servertap.utils; + +import org.jetbrains.annotations.NotNull; + +public class GsonJsonMapper implements io.javalin.plugin.json.JsonMapper { + @NotNull + @Override + public String toJsonString(@NotNull Object obj) { + return GsonSingleton.getInstance().toJson(obj); + } +} diff --git a/src/main/java/io/servertap/utils/GsonSingleton.java b/src/main/java/io/servertap/utils/GsonSingleton.java new file mode 100644 index 00000000..936a7c4c --- /dev/null +++ b/src/main/java/io/servertap/utils/GsonSingleton.java @@ -0,0 +1,19 @@ +package io.servertap.utils; + +import com.google.gson.Gson; + +public class GsonSingleton { + + private static Gson instance; + + private GsonSingleton() { + } + + public static Gson getInstance() { + if (instance == null) { + instance = new Gson(); + } + + return instance; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3ebf4a31..32365131 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,6 +2,9 @@ name: ServerTap main: io.servertap.PluginEntrypoint version: 0.1.4-SNAPSHOT description: ServerTap is a REST API for Bukkit/Spigot/PaperMC Minecraft servers. +authors: [phybros, ATechAdventurer, c1oneman, Earlh21, Scarsz, au5ton, Aberdeener, Hedlund01, matteoturini, Diddyy] +load: STARTUP +website: https://servertap.io api-version: 1.14 softdepend: - Vault