Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dependency de.chojo:cjda-util to v2.10.2+jda-5.1.0 - abandoned #54

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ 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.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
Expand Down
102 changes: 59 additions & 43 deletions bot/src/main/java/de/chojo/gamejam/api/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> 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);
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
11 changes: 6 additions & 5 deletions bot/src/main/java/de/chojo/gamejam/api/exception/Interrupt.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
112 changes: 74 additions & 38 deletions bot/src/main/java/de/chojo/gamejam/api/v1/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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> 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> 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");
}
}
}
Loading
Loading