From 5b922be4ff8907abbbcea22b43c318b718c4d689 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Aug 2024 13:05:11 +0000 Subject: [PATCH 1/3] Update dependency de.chojo:cjda-util to v2.10.2+jda-5.1.0 --- bot/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts index 343dc40..9edc344 100644 --- a/bot/build.gradle.kts +++ b/bot/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation(project(":plugin-api")) // discord - implementation("de.chojo", "cjda-util", "2.9.8+jda-5.0.0") { + implementation("de.chojo", "cjda-util", "2.10.2+jda-5.1.0") { exclude(module = "opus-java") } implementation(libs.javalin.bundle) From 6d94aa44110c077009ba8344ccc08667384ead1b Mon Sep 17 00:00:00 2001 From: Lilly <46890129+RainbowDashLabs@users.noreply.github.com> Date: Sun, 25 Aug 2024 01:07:20 +0200 Subject: [PATCH 2/3] Migrate to javalin 6 --- bot/build.gradle.kts | 5 +- .../main/java/de/chojo/gamejam/api/Api.java | 102 +++++---- .../gamejam/api/exception/Interrupt.java | 11 +- .../api/exception/InterruptException.java | 10 +- .../java/de/chojo/gamejam/api/v1/Server.java | 112 ++++++---- .../java/de/chojo/gamejam/api/v1/Teams.java | 194 +++++++++++------- .../java/de/chojo/gamejam/api/v1/Users.java | 54 ++--- .../gamejam/configuration/Configuration.java | 92 +-------- settings.gradle.kts | 5 +- 9 files changed, 299 insertions(+), 286 deletions(-) diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts index 9edc344..469d249 100644 --- a/bot/build.gradle.kts +++ b/bot/build.gradle.kts @@ -17,10 +17,13 @@ dependencies { implementation(project(":plugin-api")) // discord - implementation("de.chojo", "cjda-util", "2.10.2+jda-5.1.0") { + implementation("de.chojo", "cjda-util", "2.10.3+jda-5.1.0") { exclude(module = "opus-java") } implementation(libs.javalin.bundle) + implementation(libs.javalin.openapi) + implementation(libs.javalin.swagger) + annotationProcessor(libs.javalin.annotation) implementation("net.lingala.zip4j", "zip4j", "2.11.5") // database diff --git a/bot/src/main/java/de/chojo/gamejam/api/Api.java b/bot/src/main/java/de/chojo/gamejam/api/Api.java index 2cce371..3525320 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/Api.java +++ b/bot/src/main/java/de/chojo/gamejam/api/Api.java @@ -15,14 +15,13 @@ import de.chojo.gamejam.server.ServerService; import io.javalin.Javalin; import io.javalin.http.Context; -import io.javalin.plugin.openapi.OpenApiOptions; -import io.javalin.plugin.openapi.OpenApiPlugin; -import io.javalin.plugin.openapi.ui.ReDocOptions; -import io.javalin.plugin.openapi.ui.SwaggerOptions; +import io.javalin.http.UnauthorizedResponse; +import io.javalin.openapi.plugin.OpenApiPlugin; +import io.javalin.openapi.plugin.swagger.SwaggerPlugin; import net.dv8tion.jda.api.sharding.ShardManager; import org.slf4j.Logger; -import javax.servlet.http.HttpServletResponse; +import java.util.Set; import java.util.stream.Collectors; import static io.javalin.apibuilder.ApiBuilder.path; @@ -36,6 +35,11 @@ public class Api { private final de.chojo.gamejam.data.access.Teams teams; private final ServerService serverService; private Javalin app; + private final Set unauthorized = Set.of( + "/api/swagger", + "/api/openapi.json", + "/api/v1/server/plugin", + "/webjars"); public static Api create(Configuration configuration, ShardManager shardManager, Guilds guilds, de.chojo.gamejam.data.access.Teams teams, ServerService serverService) { var api = new Api(configuration, shardManager, guilds, teams, serverService); @@ -53,44 +57,46 @@ public Api(Configuration configuration, ShardManager shardManager, Guilds guilds private void build() { app = Javalin.create(config -> { - config.registerPlugin(getConfiguredOpenApiPlugin()); - config.accessManager((handler, ctx, routeRoles) -> { - if(ctx.path().startsWith("/swagger") || ctx.path().startsWith("/redoc") || ctx.path().startsWith("/api/v1/server/plugin")){ - handler.handle(ctx); - return; - } - - var token = ctx.req.getHeader("authorization"); - if (token == null) { - ctx.status(HttpServletResponse.SC_UNAUTHORIZED).result("Please provide a valid token in the authorization header."); - } else if (!token.equals(configuration.api().token())) { - ctx.status(HttpServletResponse.SC_UNAUTHORIZED).result("Unauthorized"); - } else { - handler.handle(ctx); - } - }); - config.requestLogger((ctx, executionTimeMs) -> { + config.useVirtualThreads = true; + config.requestLogger.http((ctx, executionTimeMs) -> { log.debug("{}: {} in {}ms\nHeaders:\n{}\nBody:\n{}", ctx.method(), ctx.path(), executionTimeMs, headers(ctx), ctx.body().substring(0, Math.min(100, ctx.body().length()))); }); + config.router.apiBuilder(this::routes); + config.registerPlugin(openApi()); + config.registerPlugin(swagger()); }).start(configuration.api().host(), configuration.api().port()); app.exception(InterruptException.class, (exception, ctx) -> { ctx.status(exception.status()).result(exception.getMessage()); }); - app.routes(() -> { - path("api/v1", () -> { - var users = new Users(shardManager, guilds); - users.routes(); - var teams = new Teams(shardManager, guilds); - teams.routes(); - Server server = new Server(serverService, this.teams); - server.routes(); - }); + app.beforeMatched(ctx -> { + for (String path : unauthorized) { + if(ctx.path().startsWith(path)) return; + } + + var token = ctx.req().getHeader("authorization"); + if (token == null) { + throw new UnauthorizedResponse(); + } else if (!token.equals(configuration.api().token())) { + throw new UnauthorizedResponse(); + } + }); + } + + private void routes() { + path("api/v1", () -> { + var users = new Users(shardManager, guilds); + users.routes(); + var teams = new Teams(shardManager, guilds); + teams.routes(); + Server server = new Server(serverService, this.teams); + server.routes(); }); + } private String headers(Context context) { @@ -101,18 +107,28 @@ private String headers(Context context) { .collect(Collectors.joining("\n ")); } - private static OpenApiPlugin getConfiguredOpenApiPlugin() { - var info = new io.swagger.v3.oas.models.info.Info().version("1.0").description("User API"); - OpenApiOptions options = new OpenApiOptions(info) - .activateAnnotationScanningFor("io.javalin.example.java") - .path("/swagger-docs") // endpoint for OpenAPI json - .swagger(new SwaggerOptions("/swagger-ui")) // endpoint for swagger-ui - .reDoc(new ReDocOptions("/redoc")) // endpoint for redoc - .defaultDocumentation(doc -> { - doc.header("authorization", String.class) - .result("401"); - }); - return new OpenApiPlugin(options); + private OpenApiPlugin openApi() { + return new OpenApiPlugin(config -> + config.withDocumentationPath("/api/openapi.json") + .withDefinitionConfiguration((version, definition) -> + definition.withInfo(info -> + info.description("Plugin Jam Bot") + .license("AGPL-3.0") + ) + .withServer(server -> + server.description("Lyna Backend") + .url(configuration.api().url())) + .withSecurity(security -> + security.withApiKeyAuth("Authorization", "Authorization")) + ) + ); + } + + private SwaggerPlugin swagger() { + return new SwaggerPlugin(conf -> { + conf.setUiPath("/api/swagger"); + conf.setDocumentationPath("/api/openapi.json"); + }); } public void shutdown() { diff --git a/bot/src/main/java/de/chojo/gamejam/api/exception/Interrupt.java b/bot/src/main/java/de/chojo/gamejam/api/exception/Interrupt.java index db1820d..5669e19 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/exception/Interrupt.java +++ b/bot/src/main/java/de/chojo/gamejam/api/exception/Interrupt.java @@ -6,24 +6,25 @@ package de.chojo.gamejam.api.exception; -import io.javalin.http.HttpCode; +import io.javalin.http.HttpStatus; import org.jetbrains.annotations.Contract; public final class Interrupt { private Interrupt() { } - public static InterruptException create(String message, HttpCode httpCode) { + public static InterruptException create(String message, HttpStatus httpCode) { return new InterruptException(message, httpCode); } @Contract("_ -> fail") public static InterruptException notFound(String entity) { - return create(String.format("%s not found.", entity), HttpCode.NOT_FOUND); + return create(String.format("%s not found.", entity), HttpStatus.NOT_FOUND); } + @Contract(" -> fail") public static InterruptException forbidden() { - return create("Endpoint forbidden", HttpCode.FORBIDDEN); + return create("Endpoint forbidden", HttpStatus.FORBIDDEN); } @Contract("null,_ -> fail") @@ -43,7 +44,7 @@ public static void assertForbidden(boolean success) throws InterruptException { @Contract(" -> fail") public static InterruptException noJam() { - return create("No current or upcoming jam", HttpCode.NOT_FOUND); + return create("No current or upcoming jam", HttpStatus.NOT_FOUND); } @Contract("true -> fail") diff --git a/bot/src/main/java/de/chojo/gamejam/api/exception/InterruptException.java b/bot/src/main/java/de/chojo/gamejam/api/exception/InterruptException.java index 4a41d71..545c40f 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/exception/InterruptException.java +++ b/bot/src/main/java/de/chojo/gamejam/api/exception/InterruptException.java @@ -6,22 +6,22 @@ package de.chojo.gamejam.api.exception; -import io.javalin.http.HttpCode; +import io.javalin.http.HttpStatus; public class InterruptException extends Exception { - private final HttpCode status; + private final HttpStatus status; - public InterruptException(String message, HttpCode status) { + public InterruptException(String message, HttpStatus status) { super(message); this.status = status; } - public InterruptException(HttpCode status) { + public InterruptException(HttpStatus status) { super(status.getMessage()); this.status = status; } public int status() { - return status.getStatus(); + return status.getCode(); } } diff --git a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java b/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java index bbbefff..2287ac1 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java +++ b/bot/src/main/java/de/chojo/gamejam/api/v1/Server.java @@ -10,7 +10,13 @@ import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; import de.chojo.gamejam.server.ServerService; import de.chojo.gamejam.server.TeamServer; -import io.javalin.http.HttpCode; +import io.javalin.http.Context; +import io.javalin.http.HttpStatus; +import io.javalin.openapi.HttpMethod; +import io.javalin.openapi.OpenApi; +import io.javalin.openapi.OpenApiParam; +import io.javalin.openapi.OpenApiResponse; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import java.io.IOException; @@ -34,47 +40,77 @@ public Server(ServerService serverService, Teams teams) { public void routes() { path("server", () -> { - post("plugin/{token}", ctx -> { - String token = ctx.pathParam("token"); - Optional team = teams.byToken(token); - if (team.isEmpty()) { - ctx.status(HttpCode.NOT_FOUND); - return; - } + post("plugin/{token}", this::handle); + }); + } - if (!ctx.contentType().equals("application/octet-stream")) { - ctx.status(HttpCode.BAD_REQUEST); - ctx.result("Use Content-Type: application/octet-stream"); - return; - } + @OpenApi(path = "/api/v1/server/plugin/{token}", + description = "Upload a plugin for the team", + headers = { + @OpenApiParam( + name = "Content-Type", + required = true, + example = "application/octet-stream", + description = "The Content-Type. Has to be application/octet-stream.")}, + methods = {HttpMethod.POST}, + pathParams = { + @OpenApiParam( + name = "token", + description = "The token of the team", + required = true)}, + queryParams = { + @OpenApiParam( + name = "restart", + description = "Whether the server should restart. Default false", + example = "true")}, + responses = { + @OpenApiResponse(status = "202", description = "When the plugin was uploaded"), + @OpenApiResponse(status = "400", description = "When the wrong Content-Type was defined"), + @OpenApiResponse(status = "404", description = "When the provided token is invalid"), + @OpenApiResponse(status = "406", description = "When the server does not exist"), + @OpenApiResponse(status = "406", description = "When the server does not exist"), + @OpenApiResponse(status = "500", description = "When the plugin was not applied successfully"), + } + ) + private void handle(@NotNull Context ctx) { + String token = ctx.pathParam("token"); + Optional team = teams.byToken(token); + if (team.isEmpty()) { + ctx.status(HttpStatus.NOT_FOUND); + return; + } - log.info("Received plugin upload request for {}", team.get()); + if (!"application/octet-stream".equals(ctx.contentType())) { + ctx.status(HttpStatus.BAD_REQUEST); + ctx.result("Use Content-Type: application/octet-stream"); + return; + } - TeamServer teamServer = serverService.get(team.get()); + log.info("Received plugin upload request for {}", team.get()); - if(!teamServer.exists()){ - ctx.result("Server does not exist"); - ctx.status(HttpCode.NOT_ACCEPTABLE); - return; - } - var pluginFile = teamServer.plugins().resolve("plugin.jar"); - try (var in = ctx.bodyAsInputStream()) { - log.info("Writing plugin to {}", pluginFile); - Files.copy(in, pluginFile, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - log.warn("Could not write file", e); - ctx.status(HttpCode.INTERNAL_SERVER_ERROR); - return; - } + TeamServer teamServer = serverService.get(team.get()); - ctx.status(HttpCode.ACCEPTED); - String restart = ctx.queryParam("restart"); - if ("true".equals(restart) && teamServer.running()) { - teamServer.restart(); - } else if (teamServer.running()) { - teamServer.send("say Plugin Updated"); - } - }); - }); + if (!teamServer.exists()) { + ctx.result("Server does not exist"); + ctx.status(HttpStatus.NOT_ACCEPTABLE); + return; + } + var pluginFile = teamServer.plugins().resolve("plugin.jar"); + try (var in = ctx.bodyInputStream()) { + log.info("Writing plugin to {}", pluginFile); + Files.copy(in, pluginFile, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + log.warn("Could not write file", e); + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR); + return; + } + + ctx.status(HttpStatus.ACCEPTED); + String restart = ctx.queryParam("restart"); + if ("true".equals(restart) && teamServer.running()) { + teamServer.restart(); + } else if (teamServer.running()) { + teamServer.send("say Plugin Updated"); + } } } diff --git a/bot/src/main/java/de/chojo/gamejam/api/v1/Teams.java b/bot/src/main/java/de/chojo/gamejam/api/v1/Teams.java index 7a0ff9a..35b0d39 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/v1/Teams.java +++ b/bot/src/main/java/de/chojo/gamejam/api/v1/Teams.java @@ -14,11 +14,12 @@ import de.chojo.gamejam.data.access.Guilds; import de.chojo.gamejam.data.dao.guild.jams.jam.teams.Team; import io.javalin.http.Context; -import io.javalin.http.HttpCode; -import io.javalin.plugin.openapi.annotations.OpenApi; -import io.javalin.plugin.openapi.annotations.OpenApiContent; -import io.javalin.plugin.openapi.annotations.OpenApiResponse; -import io.javalin.plugin.openapi.dsl.OpenApiBuilder; +import io.javalin.http.HttpStatus; +import io.javalin.openapi.HttpMethod; +import io.javalin.openapi.OpenApi; +import io.javalin.openapi.OpenApiContent; +import io.javalin.openapi.OpenApiParam; +import io.javalin.openapi.OpenApiResponse; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; @@ -42,6 +43,23 @@ public Teams(ShardManager shardManager, Guilds guilds) { this.guilds = guilds; } + /* + OpenApiBuilder.documented(OpenApiBuilder.document() + .header("leader-authorization", String.class) + .operation(operation -> { + operation.summary("Changes the name of the team."); + }).json("202", TeamProfile.class) + .result("403") + .result("409"), + */ + /* + OpenApiBuilder.documented(OpenApiBuilder.document() + .header("leader-authorization", String.class) + .operation(operation -> { + operation.summary("Changes the github link of the team."); + }).json("202", TeamProfile.class) + .result("403") + */ private static void putName(Context ctx) { // change name } @@ -50,99 +68,85 @@ private static void putGithub(Context ctx) { // change github link } + /* + OpenApiBuilder.documented(OpenApiBuilder.document() + .header("leader-authorization", String.class) + .operation(operation -> { + operation.summary("Changes the leader of the team."); + }).json("202", TeamProfile.class) + .result("403") + */ private static void putLeader(Context ctx) { // change leader } + /* + OpenApiBuilder.documented(OpenApiBuilder.document() + .header("leader-authorization", String.class) + .operation(operation -> { + operation.summary("Changes the description of the team."); + }).json("202", TeamProfile.class) + .result("403") + */ private static void putDescription(Context ctx) { // change description } + /* + OpenApiBuilder.documented(OpenApiBuilder.document() + .header("leader-authorization", String.class) + .operation(operation -> { + operation.summary("Checks if the provided authorization header is still valid"); + }).json("200", LeaderToken.class) + .result("403") + */ private void authCheck(Context ctx) throws InterruptException { String token = ctx.header("leader-authorization"); if (token == null) { - throw Interrupt.create("Set the \"leader-authorization\" header to get access.", HttpCode.UNAUTHORIZED); + throw Interrupt.create("Set the \"leader-authorization\" header to get access.", HttpStatus.UNAUTHORIZED); } Interrupt.assertForbidden("123456".equals(token)); new LeaderToken(0, token, true); - ctx.status(HttpCode.OK).result("Valid"); + ctx.status(HttpStatus.OK).result("Valid"); // returns ok if token is valid } + // OpenApi.documented(OpenApiBuilder.document() + // .operation(operation -> { + // operation.summary("Get the leader token when the user id matched the leader id") + // .description(""" + // The returned token stays valid until the leader of the team changes. + // A new request will always yield the same token as long as the leader stays. + // The token has to be send via the "leader-authorization" header.""".stripIndent()); + // }) + // .json("200", LeaderToken.class) + // .result("403") private void authorize(Context ctx) { - ctx.status(HttpCode.OK).result("123456"); + ctx.status(HttpStatus.OK).result("123456"); // returns a modification token or forbidden if id is not leader } public void routes() { path("teams", () -> { - get("{guild-id}", OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get all teams on a guild"); - }).json("200", TeamProfile[].class), - this::getGuildTeams)); + get("{guild-id}", this::getGuildTeams); path("{guild-id}/{team-id}", () -> { - get("member", OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get the member list of a team on a guild"); - }).json("200", UserProfile[].class), - this::getGuildTeamMembers)); - get("profile", OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get the profile of a team on a guild"); - }).json("200", TeamProfile.class), - this::getTeamProfile)); + get("member", this::getGuildTeamMembers); + get("profile", this::getTeamProfile); path("leader", () -> { - get("auth/{user-id}", OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get the leader token when the user id matched the leader id") - .description(""" - The returned token stays valid until the leader of the team changes. - A new request will always yield the same token as long as the leader stays. - The token has to be send via the "leader-authorization" header.""".stripIndent()); - }) - .json("200", LeaderToken.class) - .result("403"), - this::authorize)); - - get("auth/check", OpenApiBuilder.documented(OpenApiBuilder.document() - .header("leader-authorization", String.class) - .operation(operation -> { - operation.summary("Checks if the provided authorization header is still valid"); - }).json("200", LeaderToken.class) - .result("403"), this::authCheck)); + get("auth/{user-id}", this::authorize); - put("name", OpenApiBuilder.documented(OpenApiBuilder.document() - .header("leader-authorization", String.class) - .operation(operation -> { - operation.summary("Changes the name of the team."); - }).json("202", TeamProfile.class) - .result("403") - .result("409"), Teams::putName)); + get("auth/check", this::authCheck); - put("projecturl", OpenApiBuilder.documented(OpenApiBuilder.document() - .header("leader-authorization", String.class) - .operation(operation -> { - operation.summary("Changes the github link of the team."); - }).json("202", TeamProfile.class) - .result("403"), Teams::putGithub)); + put("name", Teams::putName); - put("leader", OpenApiBuilder.documented(OpenApiBuilder.document() - .header("leader-authorization", String.class) - .operation(operation -> { - operation.summary("Changes the leader of the team."); - }).json("202", TeamProfile.class) - .result("403"), Teams::putLeader)); + put("projecturl", Teams::putGithub); - put("description", OpenApiBuilder.documented(OpenApiBuilder.document() - .header("leader-authorization", String.class) - .operation(operation -> { - operation.summary("Changes the description of the team."); - }).json("202", TeamProfile.class) - .result("403"), Teams::putDescription)); + put("leader", Teams::putLeader); + + put("description", Teams::putDescription); }); }); }); @@ -182,20 +186,42 @@ private T completeEntity(RestAction action, String entity) throws Interru } } - @OpenApi(responses = { - @OpenApiResponse(status = "200", content = @OpenApiContent(from = TeamProfile[].class)) - }) + @OpenApi(path = "/api/v1/teams/{guild-id}", + description = "Get all teams on a guild", + responses = { + @OpenApiResponse( + status = "200", + content = @OpenApiContent(from = TeamProfile[].class)) + }, + pathParams = { + @OpenApiParam( + name = "guild-id", + description = "Id of the guild", + required = true)}) private void getGuildTeams(Context ctx) throws InterruptException { var guild = resolveGuild(ctx); var jamGuild = guilds.guild(guild); var jam = jamGuild.jams().nextOrCurrent(); Interrupt.assertNoJam(jam.isEmpty()); - ctx.status(HttpCode.OK).json(jam.get().teams().teams().stream().map(TeamProfile::build).toList()); + ctx.status(HttpStatus.OK).json(jam.get().teams().teams().stream().map(TeamProfile::build).toList()); } - @OpenApi(responses = { - @OpenApiResponse(status = "200", content = @OpenApiContent(from = UserProfile[].class)) - }) + @OpenApi(path = "/api/v1/teams/{guild-id}/{team-id}/member", + description = "Get the member list of a team on a guild", + methods = {HttpMethod.GET}, + responses = { + @OpenApiResponse( + status = "200", + content = @OpenApiContent(from = UserProfile[].class)), + }, + pathParams = { + @OpenApiParam( + name = "guild-id", + description = "Id of the guild", + required = true), + @OpenApiParam(name = "team-id", + description = "Id of the team", + required = true)}) private void getGuildTeamMembers(Context ctx) throws InterruptException { var teamPath = resolveTeamPath(ctx); var teamMember = teamPath.team.member(); @@ -203,15 +229,27 @@ private void getGuildTeamMembers(Context ctx) throws InterruptException { for (var member : teamMember) { members.add(getMember(teamPath.guild(), member.member().getIdLong())); } - ctx.status(HttpCode.OK).json(members.stream().map(UserProfile::build).toList()); + ctx.status(HttpStatus.OK).json(members.stream().map(UserProfile::build).toList()); } - @OpenApi(responses = { - @OpenApiResponse(status = "200", content = @OpenApiContent(from = TeamProfile.class)) - }) + @OpenApi(path = "/api/v1/teams/{guild-id}/{team-id}/profile", + responses = { + @OpenApiResponse( + status = "200", + content = @OpenApiContent(from = TeamProfile.class)), + + }, + pathParams = { + @OpenApiParam( + name = "guild-id", + description = "Id of the guild", + required = true), + @OpenApiParam(name = "team-id", + description = "Id of the team", + required = true)}) private void getTeamProfile(Context ctx) throws InterruptException { var teamPath = resolveTeamPath(ctx); - ctx.status(HttpCode.OK).json(TeamProfile.build(teamPath.team())); + ctx.status(HttpStatus.OK).json(TeamProfile.build(teamPath.team())); } private record LeaderPath(Team team, Guild guild) { diff --git a/bot/src/main/java/de/chojo/gamejam/api/v1/Users.java b/bot/src/main/java/de/chojo/gamejam/api/v1/Users.java index 05e1ebb..cf27c8a 100644 --- a/bot/src/main/java/de/chojo/gamejam/api/v1/Users.java +++ b/bot/src/main/java/de/chojo/gamejam/api/v1/Users.java @@ -12,14 +12,12 @@ import de.chojo.gamejam.api.v1.wrapper.TeamProfile; import de.chojo.gamejam.api.v1.wrapper.UserProfile; import de.chojo.gamejam.data.access.Guilds; -import de.chojo.gamejam.data.dao.JamGuild; import io.javalin.http.Context; -import io.javalin.http.HttpCode; -import io.javalin.plugin.openapi.annotations.HttpMethod; -import io.javalin.plugin.openapi.annotations.OpenApi; -import io.javalin.plugin.openapi.annotations.OpenApiContent; -import io.javalin.plugin.openapi.annotations.OpenApiResponse; -import io.javalin.plugin.openapi.dsl.OpenApiBuilder; +import io.javalin.http.HttpStatus; +import io.javalin.openapi.HttpMethod; +import io.javalin.openapi.OpenApi; +import io.javalin.openapi.OpenApiContent; +import io.javalin.openapi.OpenApiResponse; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; @@ -42,25 +40,11 @@ public Users(ShardManager shardManager, Guilds guilds) { public void routes() { path("users", () -> { path("{user-id}", () -> { - path("guilds", () -> { - get(OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get the mututal guilds with the user"); - }).json("200", GuildProfile[].class), - this::getMututalGuilds)); - }); + path("guilds", () -> + get(this::getMutualGuilds)); path("{guild-id}", () -> { - get("team", OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get the team of a user on a guild"); - }).json("200", TeamProfile.class), - this::getUserTeam)); - - get("profile", OpenApiBuilder.documented(OpenApiBuilder.document() - .operation(operation -> { - operation.summary("Get the user profile of a user on a guild"); - }).json("200", UserProfile.class), - this::getUserProfile)); + get("team", this::getUserTeam); + get("profile", this::getUserProfile); }); }); }); @@ -93,17 +77,23 @@ private T completeEntity(RestAction action, String entity) throws Interru } } - private void getMututalGuilds(Context ctx) throws InterruptException { + @OpenApi(path = "/api/v1/users/{user-id}/{guild-id}/guild", + summary = "Get the user profile of a user on a guild", + methods = HttpMethod.GET, + responses = { + @OpenApiResponse(status = "200", content = {@OpenApiContent(from = GuildProfile[].class)}) + }) + private void getMutualGuilds(Context ctx) throws InterruptException { var user = getUser(ctx.pathParamAsClass("user-id", Long.class).get()); var guildProfiles = shardManager.getMutualGuilds(user).stream().map(GuildProfile::build).toList(); - ctx.status(HttpCode.OK).json(guildProfiles); + ctx.status(HttpStatus.OK).json(guildProfiles); } @OpenApi(path = "/api/v1/users/{user-id}/{guild-id}/team", - method = HttpMethod.GET, + methods = HttpMethod.GET, responses = { - @OpenApiResponse(status = "200", content = @OpenApiContent(from = TeamProfile.class)) + @OpenApiResponse(status = "200", content = {@OpenApiContent(from = TeamProfile.class)}) }) private void getUserTeam(Context ctx) throws InterruptException { var guildPath = resolveGuildPath(ctx); @@ -116,16 +106,16 @@ private void getUserTeam(Context ctx) throws InterruptException { Interrupt.assertNotFound(team.isEmpty(), "Team"); - ctx.status(HttpCode.OK).json(TeamProfile.build(team.get())); + ctx.status(HttpStatus.OK).json(TeamProfile.build(team.get())); } @OpenApi(path = "/api/v1/users/{user-id}/{guild-id}/profile", - method = HttpMethod.GET, + methods = HttpMethod.GET, responses = { @OpenApiResponse(status = "200", content = @OpenApiContent(from = UserProfile.class)) }) private void getUserProfile(Context ctx) throws InterruptException { - ctx.status(HttpCode.OK).json(UserProfile.build(resolveGuildPath(ctx).member())); + ctx.status(HttpStatus.OK).json(UserProfile.build(resolveGuildPath(ctx).member())); } private record GuildPath(Member member, Guild guild) { diff --git a/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java b/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java index 7e0f7a4..ab52e43 100644 --- a/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java +++ b/bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java @@ -6,43 +6,17 @@ package de.chojo.gamejam.configuration; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; import de.chojo.gamejam.configuration.elements.Api; import de.chojo.gamejam.configuration.elements.BaseSettings; import de.chojo.gamejam.configuration.elements.Database; import de.chojo.gamejam.configuration.elements.Plugins; import de.chojo.gamejam.configuration.elements.ServerManagement; import de.chojo.gamejam.configuration.elements.ServerTemplate; -import de.chojo.gamejam.configuration.exception.ConfigurationException; -import org.slf4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import static org.slf4j.LoggerFactory.getLogger; - -public class Configuration { - private static final Logger log = getLogger(Configuration.class); - private final ObjectMapper objectMapper; - private ConfigFile configFile; +import de.chojo.jdautil.configuration.BaseConfiguration; +public class Configuration extends BaseConfiguration { private Configuration() { - objectMapper = JsonMapper.builder() - .configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, true) - .build() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE) - .setDefaultPrettyPrinter(new DefaultPrettyPrinter()); + super(new ConfigFile()); } public static Configuration create() { @@ -51,76 +25,28 @@ public static Configuration create() { return configuration; } - public void reload() { - try { - reloadFile(); - } catch (IOException e) { - log.info("Could not load config", e); - throw new ConfigurationException("Could not load config file", e); - } - try { - save(); - } catch (IOException e) { - log.error("Could not save config.", e); - } - } - - private void save() throws IOException { - try (var sequenceWriter = objectMapper.writerWithDefaultPrettyPrinter() - .writeValues(getConfig().toFile())) { - sequenceWriter.write(configFile); - } - } - - private void reloadFile() throws IOException { - forceConsistency(); - configFile = objectMapper.readValue(getConfig().toFile(), ConfigFile.class); - } - - private void forceConsistency() throws IOException { - Files.createDirectories(getConfig().getParent()); - if (!getConfig().toFile().exists()) { - if (getConfig().toFile().createNewFile()) { - try(var sequenceWriter = objectMapper.writerWithDefaultPrettyPrinter() - .writeValues(getConfig().toFile())) { - sequenceWriter.write(new ConfigFile()); - throw new ConfigurationException("Please configure the config."); - } - } - } - } - - private Path getConfig() { - var home = new File(".").getAbsoluteFile().getParentFile().toPath(); - var property = System.getProperty("bot.config"); - if (property == null) { - log.error("bot.config property is not set."); - throw new ConfigurationException("Property -Dbot.config= is not set."); - } - return Paths.get(home.toString(), property); - } public Database database() { - return configFile.database(); + return config().database(); } public BaseSettings baseSettings() { - return configFile.baseSettings(); + return config().baseSettings(); } public Api api() { - return configFile.api(); + return config().api(); } public ServerManagement serverManagement() { - return configFile.serverManagement(); + return config().serverManagement(); } public Plugins plugins() { - return configFile.plugins(); + return config().plugins(); } public ServerTemplate serverTemplate() { - return configFile.serverTemplate(); + return config().serverTemplate(); } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5b5fbd3..3510f72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,9 +23,12 @@ dependencyResolutionManagement { bundle("logging", listOf("slf4j", "log4j-core", "log4j-slf4j2")) - version("javalin", "4.6.8") + version("javalin", "6.3.0") library("javalin-core", "io.javalin", "javalin").versionRef("javalin") library("javalin-bundle", "io.javalin", "javalin-bundle").versionRef("javalin") + library("javalin-annotation", "io.javalin.community.openapi", "openapi-annotation-processor").versionRef("javalin") + library("javalin-openapi", "io.javalin.community.openapi", "javalin-openapi-plugin").versionRef("javalin") + library("javalin-swagger", "io.javalin.community.openapi", "javalin-swagger-plugin").versionRef("javalin") version("eldoutil", "2.1.5") library("eldoutil-plugin", "de.eldoria.util", "plugin").versionRef("eldoutil") From 99b83c36c765275788a492e273acc4fc1138f913 Mon Sep 17 00:00:00 2001 From: Lilly <46890129+RainbowDashLabs@users.noreply.github.com> Date: Sun, 25 Aug 2024 01:16:47 +0200 Subject: [PATCH 3/3] Patch plugins to javalin 6 --- .../main/java/de/chojo/pluginjam/api/Api.java | 31 ++++++++++--------- .../pluginjam/api/routes/Configuration.java | 12 +++---- .../main/java/de/chojo/pluginjam/web/Api.java | 30 +++++++++--------- .../de/chojo/pluginjam/web/server/Server.java | 10 +++--- 4 files changed, 44 insertions(+), 39 deletions(-) diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java index dec6e7d..7f2f9a6 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/Api.java @@ -21,35 +21,38 @@ public class Api { private static final Logger log = getLogger(Api.class); - private final Javalin javalin; private final Configuration configuration; private final Stats stats; private final Requests requests; + private Javalin javalin; - private Api(Javalin javalin, Plugin plugin, ServerRequests serverRequests) { - this.javalin = javalin; + private Api(Plugin plugin, ServerRequests serverRequests) { configuration = new Configuration(plugin); stats = new Stats(plugin); requests = new Requests(serverRequests); } public static Api create(Plugin plugin, ServerRequests serverRequests) { - var classLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(PluginJam.class.getClassLoader()); - var javalin = Javalin.create(); - javalin.start("0.0.0.0", Integer.parseInt(System.getProperty("javalin.port", "30000"))); - Thread.currentThread().setContextClassLoader(classLoader); - var api = new Api(javalin, plugin, serverRequests); + var api = new Api(plugin, serverRequests); api.ignite(); return api; } private void ignite() { - javalin.routes(() -> { - before(ctx -> log.debug("Received request on {}.", ctx.path())); - path("v1", configuration::buildRoutes); - path("v1", stats::buildRoutes); - path("v1", requests::buildRoutes); + var classLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(PluginJam.class.getClassLoader()); + javalin = Javalin.create(config -> { + config.useVirtualThreads = true; + config.router.apiBuilder(this::routes); }); + javalin.start("0.0.0.0", Integer.parseInt(System.getProperty("javalin.port", "30000"))); + Thread.currentThread().setContextClassLoader(classLoader); + } + + private void routes() { + before(ctx -> log.debug("Received request on {}.", ctx.path())); + path("v1", configuration::buildRoutes); + path("v1", stats::buildRoutes); + path("v1", requests::buildRoutes); } } diff --git a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java index 0ff9cdc..526b5f8 100644 --- a/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java +++ b/plugin-paper/src/main/java/de/chojo/pluginjam/api/routes/Configuration.java @@ -6,7 +6,7 @@ package de.chojo.pluginjam.api.routes; -import io.javalin.http.HttpCode; +import io.javalin.http.HttpStatus; import org.bukkit.plugin.Plugin; import static io.javalin.apibuilder.ApiBuilder.path; @@ -24,30 +24,30 @@ public void buildRoutes() { post("message", ctx -> { plugin.getConfig().set("message", ctx.body()); plugin.saveConfig(); - ctx.status(HttpCode.OK); + ctx.status(HttpStatus.OK); }); post("maxplayers", ctx -> { plugin.getConfig().set("maxplayers", Integer.parseInt(ctx.body())); plugin.saveConfig(); - ctx.status(HttpCode.OK); + ctx.status(HttpStatus.OK); }); post("spectatoroverflow", ctx -> { plugin.getConfig().set("spectatoroverflow", Boolean.parseBoolean(ctx.body())); plugin.saveConfig(); - ctx.status(HttpCode.OK); + ctx.status(HttpStatus.OK); }); post("reviewmode", ctx -> { plugin.getConfig().set("spectatoroverflow", Boolean.parseBoolean(ctx.body())); plugin.saveConfig(); - ctx.status(HttpCode.OK); + ctx.status(HttpStatus.OK); }); post("whitelist", ctx -> { plugin.getServer().getScheduler().runTask(plugin, () -> plugin.getServer().setWhitelist(Boolean.parseBoolean(ctx.body()))); - ctx.status(HttpCode.OK); + ctx.status(HttpStatus.OK); }); }); } diff --git a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/Api.java b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/Api.java index 2f92557..e59d484 100644 --- a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/Api.java +++ b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/Api.java @@ -6,9 +6,7 @@ package de.chojo.pluginjam.web; -import com.velocitypowered.api.proxy.ProxyServer; import de.chojo.pluginjam.PluginJam; -import de.chojo.pluginjam.configuration.Configuration; import de.chojo.pluginjam.servers.ServerRegistry; import de.chojo.pluginjam.web.server.Server; import io.javalin.Javalin; @@ -20,30 +18,34 @@ public class Api { private static final Logger log = getLogger(Api.class); - private final Javalin javalin; private final Server server; + private Javalin javalin; - private Api(ServerRegistry registry, Javalin javalin) { - this.javalin = javalin; + private Api(ServerRegistry registry) { this.server = new Server(registry); } public static Api create(ServerRegistry registry) { + var api = new Api(registry); + api.ignite(); + return api; + } + + private void ignite() { var classLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(PluginJam.class.getClassLoader()); - var javalin = Javalin.create(); + javalin = Javalin.create(config -> { + config.useVirtualThreads = true; + config.router.apiBuilder(this::routes); + }); int port = Integer.parseInt(System.getProperty("javalin.port", "30000")); javalin.start("0.0.0.0", port); Thread.currentThread().setContextClassLoader(classLoader); - var api = new Api(registry, javalin); - api.ignite(); - return api; + } - private void ignite() { - javalin.routes(() -> { - before(ctx -> log.debug("Received request on {}.", ctx.path())); - path("v1", server::buildRoutes); - }); + private void routes() { + before(ctx -> log.debug("Received request on {}.", ctx.path())); + path("v1", server::buildRoutes); } } diff --git a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java index 6ca9388..40e7d6f 100644 --- a/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java +++ b/plugin-velocity/src/main/java/de/chojo/pluginjam/web/server/Server.java @@ -8,7 +8,7 @@ import de.chojo.pluginjam.payload.Registration; import de.chojo.pluginjam.servers.ServerRegistry; -import io.javalin.http.HttpCode; +import io.javalin.http.HttpStatus; import static io.javalin.apibuilder.ApiBuilder.delete; import static io.javalin.apibuilder.ApiBuilder.get; @@ -27,12 +27,12 @@ public void buildRoutes() { path("server", () -> { post("", ctx -> { registry.register(ctx.bodyAsClass(Registration.class)); - ctx.status(HttpCode.ACCEPTED); + ctx.status(HttpStatus.ACCEPTED); }); patch("", ctx -> { registry.ping(ctx.bodyAsClass(Registration.class)); - ctx.status(HttpCode.ACCEPTED); + ctx.status(HttpStatus.ACCEPTED); }); delete("", ctx -> { @@ -41,12 +41,12 @@ public void buildRoutes() { var host = ctx.queryParam("host"); var registration = new Registration(Integer.parseInt(id), "", host, Integer.parseInt(port), 0); registry.unregister(registration); - ctx.status(HttpCode.ACCEPTED); + ctx.status(HttpStatus.ACCEPTED); }); get("", ctx -> { ctx.json(registry.server()); - ctx.status(HttpCode.OK); + ctx.status(HttpStatus.OK); }); }); }