a
This commit is contained in:
parent
a1e69b84fa
commit
dca5ad1d93
15 changed files with 275 additions and 360 deletions
57
README.md
57
README.md
|
@ -1,54 +1,3 @@
|
||||||
# mstats
|
- `/api/server/heartbeat` - server heartbeat
|
||||||
|
- `/api/plugin/{id}` - plugin info
|
||||||
This project uses Quarkus, the Supersonic Subatomic Java Framework.
|
- `/api/plugin/find/{name}` - find a plugin id by name
|
||||||
|
|
||||||
If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
|
|
||||||
|
|
||||||
## Running the application in dev mode
|
|
||||||
|
|
||||||
You can run your application in dev mode that enables live coding using:
|
|
||||||
|
|
||||||
```shell script
|
|
||||||
./mvnw compile quarkus:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at <http://localhost:8080/q/dev/>.
|
|
||||||
|
|
||||||
## Packaging and running the application
|
|
||||||
|
|
||||||
The application can be packaged using:
|
|
||||||
|
|
||||||
```shell script
|
|
||||||
./mvnw package
|
|
||||||
```
|
|
||||||
|
|
||||||
It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory.
|
|
||||||
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory.
|
|
||||||
|
|
||||||
The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.
|
|
||||||
|
|
||||||
If you want to build an _über-jar_, execute the following command:
|
|
||||||
|
|
||||||
```shell script
|
|
||||||
./mvnw package -Dquarkus.package.jar.type=uber-jar
|
|
||||||
```
|
|
||||||
|
|
||||||
The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`.
|
|
||||||
|
|
||||||
## Creating a native executable
|
|
||||||
|
|
||||||
You can create a native executable using:
|
|
||||||
|
|
||||||
```shell script
|
|
||||||
./mvnw package -Dnative
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
|
|
||||||
|
|
||||||
```shell script
|
|
||||||
./mvnw package -Dnative -Dquarkus.native.container-build=true
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then execute your native executable with: `./target/mstats-0.0.1-SNAPSHOT-runner`
|
|
||||||
|
|
||||||
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/maven-tooling>.
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.m724.mstats;
|
package eu.m724.mstats;
|
||||||
|
|
||||||
import eu.m724.mstats.auth.AuthService;
|
import eu.m724.mstats.api.service.PluginService;
|
||||||
import eu.m724.mstats.orm.Administrator;
|
|
||||||
import io.quarkus.runtime.StartupEvent;
|
import io.quarkus.runtime.StartupEvent;
|
||||||
import jakarta.enterprise.event.Observes;
|
import jakarta.enterprise.event.Observes;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
@ -9,11 +8,10 @@ import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
public class Startup {
|
public class Startup {
|
||||||
@Inject
|
@Inject
|
||||||
AuthService authService;
|
PluginService pluginService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void onStartup(@Observes StartupEvent event) {
|
public void onStartup(@Observes StartupEvent event) {
|
||||||
Administrator administrator = Administrator.createAdministrator();
|
pluginService.createPlugin("ploogin");
|
||||||
System.out.println(administrator.getTokenEncoded());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package eu.m724.mstats.api.resource;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import eu.m724.mstats.api.service.PluginService;
|
||||||
|
import eu.m724.mstats.orm.Plugin;
|
||||||
|
import eu.m724.mstats.orm.Server;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Path("/api/plugin")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public class PluginApiResource {
|
||||||
|
@Inject
|
||||||
|
PluginService pluginService;
|
||||||
|
|
||||||
|
@Path("/{id}")
|
||||||
|
@GET
|
||||||
|
public Response stats(Long id) {
|
||||||
|
Plugin plugin = pluginService.getPlugin(id);
|
||||||
|
if (plugin == null) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Integer> servers = new HashMap<>();
|
||||||
|
int serversTotal = 0;
|
||||||
|
|
||||||
|
Map<String, Integer> versions = new HashMap<>();
|
||||||
|
for (Server server : plugin.servers) {
|
||||||
|
String pluginVersion = server.plugins.stream().filter(p -> p.plugin.equals(plugin)).findFirst().get().version;
|
||||||
|
serversTotal++;
|
||||||
|
versions.put(pluginVersion, versions.getOrDefault(pluginVersion, 0) + 1);
|
||||||
|
servers.put(server.serverVersion, servers.getOrDefault(server.serverVersion, 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsResponse statsResponse = new StatsResponse();
|
||||||
|
statsResponse.id = id;
|
||||||
|
statsResponse.name = plugin.name;
|
||||||
|
statsResponse.servers = serversTotal;
|
||||||
|
statsResponse.serverVersions = servers.entrySet().stream().map(e -> new Version(e.getKey(), e.getValue())).toList();
|
||||||
|
statsResponse.pluginVersions = versions.entrySet().stream().map(e -> new Version(e.getKey(), e.getValue())).toList();
|
||||||
|
|
||||||
|
return Response.ok(statsResponse).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Path("/find/{name}")
|
||||||
|
@GET
|
||||||
|
public Response find(String name) {
|
||||||
|
Plugin plugin = Plugin.find("name", name).firstResult();
|
||||||
|
if (plugin == null) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
return Response.ok(new ObjectMapper().createObjectNode().put("id", plugin.id)).build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatsResponse {
|
||||||
|
@JsonProperty("id")
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@JsonProperty("name")
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@JsonProperty("servers")
|
||||||
|
public int servers;
|
||||||
|
|
||||||
|
@JsonProperty("serverVersions")
|
||||||
|
public List<Version> serverVersions;
|
||||||
|
|
||||||
|
@JsonProperty("pluginVersions")
|
||||||
|
public List<Version> pluginVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Version {
|
||||||
|
public Version(String version, int servers) {
|
||||||
|
this.version = version;
|
||||||
|
this.servers = servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("version")
|
||||||
|
public String version;
|
||||||
|
|
||||||
|
@JsonProperty("servers")
|
||||||
|
public int servers;
|
||||||
|
}
|
||||||
|
}
|
100
src/main/java/eu/m724/mstats/api/resource/ServerApiResource.java
Normal file
100
src/main/java/eu/m724/mstats/api/resource/ServerApiResource.java
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package eu.m724.mstats.api.resource;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||||
|
import com.fasterxml.jackson.annotation.Nulls;
|
||||||
|
import eu.m724.mstats.orm.Server;
|
||||||
|
import eu.m724.mstats.api.service.ServerService;
|
||||||
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Path("/api/server")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public class ServerApiResource {
|
||||||
|
@Inject
|
||||||
|
ServerService serverService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SecurityIdentity identity;
|
||||||
|
|
||||||
|
@Path("/heartbeat")
|
||||||
|
@POST
|
||||||
|
public Response heartbeat(HeartbeatRequest heartbeatRequest) {
|
||||||
|
if (heartbeatRequest == null)
|
||||||
|
heartbeatRequest = new HeartbeatRequest();
|
||||||
|
|
||||||
|
HeartbeatResponse heartbeatResponse = new HeartbeatResponse();
|
||||||
|
Server server;
|
||||||
|
|
||||||
|
if (identity.isAnonymous()) {
|
||||||
|
server = serverService.createServer();
|
||||||
|
heartbeatResponse.token = server.getTokenEncoded();
|
||||||
|
} else {
|
||||||
|
server = identity.getAttribute("server");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heartbeatRequest.statsVersion != 1)
|
||||||
|
heartbeatResponse.version = 1;
|
||||||
|
|
||||||
|
serverService.heartbeat(server, heartbeatRequest);
|
||||||
|
|
||||||
|
return Response.ok(heartbeatResponse).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HeartbeatRequest {
|
||||||
|
/** List of registered plugins, this is sent only on boot */
|
||||||
|
@JsonProperty("plugins")
|
||||||
|
public List<RunningPlugin> plugins = new ArrayList<>();
|
||||||
|
|
||||||
|
/** Server version like 1.21.1, this is sent only on boot */
|
||||||
|
@JsonProperty("serverVersion")
|
||||||
|
public String serverVersion;
|
||||||
|
|
||||||
|
/** Client version */
|
||||||
|
@JsonProperty("statsVersion")
|
||||||
|
public int statsVersion;
|
||||||
|
|
||||||
|
public HeartbeatRequest() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public static class HeartbeatResponse {
|
||||||
|
/** New token assigned to the server, usually after first request */
|
||||||
|
@JsonProperty("token")
|
||||||
|
@JsonSetter(nulls = Nulls.SKIP)
|
||||||
|
public String token = null;
|
||||||
|
|
||||||
|
/** A message that will be displayed to server console */
|
||||||
|
@JsonProperty("message")
|
||||||
|
@JsonSetter(nulls = Nulls.SKIP)
|
||||||
|
public String message = null;
|
||||||
|
|
||||||
|
/** Server version, used only if it doesn't match the client version */
|
||||||
|
@JsonProperty("version")
|
||||||
|
@JsonSetter(nulls = Nulls.SKIP)
|
||||||
|
public Integer version = null;
|
||||||
|
|
||||||
|
public HeartbeatResponse() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RunningPlugin {
|
||||||
|
public RunningPlugin() {}
|
||||||
|
|
||||||
|
@JsonProperty("id")
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@JsonProperty("version")
|
||||||
|
public String version;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.m724.mstats.resource.api;
|
package eu.m724.mstats.api.service;
|
||||||
|
|
||||||
import eu.m724.mstats.orm.Plugin;
|
import eu.m724.mstats.orm.Plugin;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
@ -7,8 +7,8 @@ import jakarta.transaction.Transactional;
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class PluginService {
|
public class PluginService {
|
||||||
@Transactional
|
@Transactional
|
||||||
public Plugin createPlugin(String name, String description) {
|
public Plugin createPlugin(String name) {
|
||||||
return Plugin.createPlugin(name, description);
|
return Plugin.createPlugin(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -29,7 +29,7 @@ public class PluginService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Plugin editPlugin(long id, String name, String description) {
|
public Plugin editPlugin(long id, String name) {
|
||||||
Plugin plugin = Plugin.findById(id);
|
Plugin plugin = Plugin.findById(id);
|
||||||
|
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
|
@ -38,9 +38,6 @@ public class PluginService {
|
||||||
if (name != null)
|
if (name != null)
|
||||||
plugin.name = name;
|
plugin.name = name;
|
||||||
|
|
||||||
if (description != null)
|
|
||||||
plugin.description = description;
|
|
||||||
|
|
||||||
plugin.persistAndFlush();
|
plugin.persistAndFlush();
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
36
src/main/java/eu/m724/mstats/api/service/ServerService.java
Normal file
36
src/main/java/eu/m724/mstats/api/service/ServerService.java
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package eu.m724.mstats.api.service;
|
||||||
|
|
||||||
|
import eu.m724.mstats.api.resource.ServerApiResource;
|
||||||
|
import eu.m724.mstats.orm.Server;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ServerService {
|
||||||
|
@Transactional
|
||||||
|
public Server createServer() {
|
||||||
|
return Server.createServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void modifyServer(Server server) {
|
||||||
|
server.persistAndFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void heartbeat(Server server, ServerApiResource.HeartbeatRequest heartbeatRequest) {
|
||||||
|
server = Server.findById(server.id); // this is necessary for some reason
|
||||||
|
|
||||||
|
for (ServerApiResource.RunningPlugin plugin : heartbeatRequest.plugins) {
|
||||||
|
server.addPlugin(plugin.id, plugin.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heartbeatRequest.serverVersion != null)
|
||||||
|
server.serverVersion = heartbeatRequest.serverVersion;
|
||||||
|
|
||||||
|
server.heartbeat = Instant.now();
|
||||||
|
server.persistAndFlush();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.m724.mstats.auth;
|
package eu.m724.mstats.auth;
|
||||||
|
|
||||||
import eu.m724.mstats.orm.Administrator;
|
|
||||||
import eu.m724.mstats.orm.Server;
|
import eu.m724.mstats.orm.Server;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
@ -9,17 +8,9 @@ import java.util.Base64;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class AuthService {
|
public class AuthService {
|
||||||
public final Base64.Decoder decoder = Base64.getDecoder();
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
Administrator getAdministratorByToken(String encoded) {
|
|
||||||
byte[] token = decoder.decode(encoded);
|
|
||||||
return Administrator.find("token", (Object) token).firstResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
Server getServerByToken(String encoded) {
|
Server getServerByToken(String encoded) {
|
||||||
byte[] token = decoder.decode(encoded);
|
byte[] token = Base64.getDecoder().decode(encoded);
|
||||||
return Server.find("token", (Object) token).firstResult();
|
return Server.find("token", (Object) token).firstResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package eu.m724.mstats.auth;
|
package eu.m724.mstats.auth;
|
||||||
|
|
||||||
|
|
||||||
import eu.m724.mstats.orm.Administrator;
|
|
||||||
import eu.m724.mstats.orm.Server;
|
import eu.m724.mstats.orm.Server;
|
||||||
import io.quarkus.security.identity.IdentityProviderManager;
|
import io.quarkus.security.identity.IdentityProviderManager;
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
@ -16,7 +14,6 @@ import jakarta.annotation.Priority;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.enterprise.inject.Alternative;
|
import jakarta.enterprise.inject.Alternative;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.core.Response;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -40,29 +37,6 @@ public class MyHttpAuthenticationMechanism implements HttpAuthenticationMechanis
|
||||||
.addRole("server")
|
.addRole("server")
|
||||||
.addAttribute("server", server)
|
.addAttribute("server", server)
|
||||||
.build();
|
.build();
|
||||||
} else {
|
|
||||||
context.response()
|
|
||||||
.setStatusCode(Response.Status.UNAUTHORIZED.getStatusCode())
|
|
||||||
.setStatusMessage("{\"message\": \"X-Server-Token is invalid\"}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String adminTokenEncoded = context.request().getHeader("X-Administrator-Token");
|
|
||||||
if (adminTokenEncoded != null) {
|
|
||||||
Administrator administrator = authService.getAdministratorByToken(adminTokenEncoded);
|
|
||||||
if (administrator != null) {
|
|
||||||
return QuarkusSecurityIdentity.builder()
|
|
||||||
.setPrincipal(new QuarkusPrincipal("Administrator " + administrator.id.toString()))
|
|
||||||
.addRole("administrator")
|
|
||||||
.addRoles(administrator.roles)
|
|
||||||
.addAttribute("administrator", administrator)
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
context.response()
|
|
||||||
.setStatusCode(Response.Status.UNAUTHORIZED.getStatusCode())
|
|
||||||
.setStatusMessage("{\"message\": \"X-Administrator-Token is invalid\"}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
package eu.m724.mstats.orm;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(
|
|
||||||
uniqueConstraints = @UniqueConstraint(columnNames = "token"),
|
|
||||||
indexes = @Index(columnList = "token")
|
|
||||||
)
|
|
||||||
public class Administrator extends PanacheEntity {
|
|
||||||
@Column(nullable = false)
|
|
||||||
public byte[] token;
|
|
||||||
|
|
||||||
public Set<String> roles;
|
|
||||||
|
|
||||||
public String getTokenEncoded() {
|
|
||||||
return Base64.getEncoder().encodeToString(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public static Administrator createAdministrator(String... roles) {
|
|
||||||
byte[] token = new byte[32];
|
|
||||||
ThreadLocalRandom.current().nextBytes(token);
|
|
||||||
|
|
||||||
Administrator administrator = new Administrator();
|
|
||||||
administrator.token = token;
|
|
||||||
administrator.roles = Set.of(roles);
|
|
||||||
|
|
||||||
administrator.persistAndFlush();
|
|
||||||
|
|
||||||
return administrator;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +1,30 @@
|
||||||
package eu.m724.mstats.orm;
|
package eu.m724.mstats.orm;
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.*;
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.ManyToMany;
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@Table(
|
||||||
|
indexes = @Index(columnList = "name")
|
||||||
|
)
|
||||||
public class Plugin extends PanacheEntity {
|
public class Plugin extends PanacheEntity {
|
||||||
@Column(unique = true, nullable = false)
|
@Column(unique = true, nullable = false)
|
||||||
public String name;
|
public String name;
|
||||||
|
|
||||||
public String description;
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
|
|
||||||
@ManyToMany
|
|
||||||
public List<Server> servers = new ArrayList<>();
|
public List<Server> servers = new ArrayList<>();
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public static Plugin createPlugin(String name, String description) {
|
public static Plugin createPlugin(String name) {
|
||||||
Plugin plugin = new Plugin();
|
Plugin plugin = new Plugin();
|
||||||
plugin.name = name;
|
plugin.name = name;
|
||||||
plugin.description = description;
|
|
||||||
|
|
||||||
plugin.persistAndFlush();
|
plugin.persistAndFlush();
|
||||||
|
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public static Plugin createPlugin(String name) {
|
|
||||||
return createPlugin(name, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
19
src/main/java/eu/m724/mstats/orm/PluginWithVersion.java
Normal file
19
src/main/java/eu/m724/mstats/orm/PluginWithVersion.java
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package eu.m724.mstats.orm;
|
||||||
|
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class PluginWithVersion extends PanacheEntity {
|
||||||
|
public PluginWithVersion() {}
|
||||||
|
|
||||||
|
public PluginWithVersion(Plugin plugin, String version) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
public Plugin plugin;
|
||||||
|
|
||||||
|
public String version;
|
||||||
|
}
|
|
@ -19,13 +19,10 @@ public class Server extends PanacheEntity {
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
public byte[] token;
|
public byte[] token;
|
||||||
|
|
||||||
/**
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
* ISO 3166-1 numeric
|
public List<PluginWithVersion> plugins = new ArrayList<>();
|
||||||
*/
|
|
||||||
public int countryCode;
|
|
||||||
|
|
||||||
@ManyToMany(cascade = CascadeType.ALL)
|
public String serverVersion;
|
||||||
public List<Plugin> plugins = new ArrayList<>();
|
|
||||||
|
|
||||||
public Instant heartbeat = Instant.now();
|
public Instant heartbeat = Instant.now();
|
||||||
|
|
||||||
|
@ -34,13 +31,16 @@ public class Server extends PanacheEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Plugin addPlugin(long id) {
|
public Plugin addPlugin(long id, String version) {
|
||||||
Plugin plugin = Plugin.findById(id);
|
Plugin plugin = Plugin.findById(id);
|
||||||
|
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
plugins.add(plugin);
|
if (plugins.stream().anyMatch(pwv -> pwv.plugin.id == id))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
plugins.add(new PluginWithVersion(plugin, version));
|
||||||
plugin.servers.add(this);
|
plugin.servers.add(this);
|
||||||
|
|
||||||
this.persistAndFlush();
|
this.persistAndFlush();
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
package eu.m724.mstats.resource.api;
|
|
||||||
|
|
||||||
import eu.m724.mstats.auth.AuthService;
|
|
||||||
import eu.m724.mstats.orm.Plugin;
|
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
|
||||||
import io.vertx.core.json.JsonObject;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.ws.rs.*;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import jakarta.ws.rs.core.Response;
|
|
||||||
|
|
||||||
@Path("/api/admin")
|
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
@RolesAllowed("administrator")
|
|
||||||
public class AdminApiResource {
|
|
||||||
@Inject
|
|
||||||
AuthService authService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
PluginService pluginService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SecurityIdentity identity;
|
|
||||||
|
|
||||||
@Path("/plugin")
|
|
||||||
@GET
|
|
||||||
public Response getPlugin(@QueryParam("id") long id) {
|
|
||||||
Plugin plugin = pluginService.getPlugin(id);
|
|
||||||
|
|
||||||
if (plugin == null)
|
|
||||||
return Response.ok(new JsonObject().put("id", id)).status(Response.Status.NOT_FOUND).build();
|
|
||||||
|
|
||||||
JsonObject response = new JsonObject()
|
|
||||||
.put("id", plugin.id)
|
|
||||||
.put("name", plugin.name)
|
|
||||||
.put("description", plugin.description);
|
|
||||||
|
|
||||||
return Response.ok(response).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("/plugin")
|
|
||||||
@PUT
|
|
||||||
public Response createPlugin(JsonObject data) {
|
|
||||||
String name = data.getString("name");
|
|
||||||
String description = data.getString("description");
|
|
||||||
|
|
||||||
Plugin plugin = pluginService.createPlugin(name, description);
|
|
||||||
|
|
||||||
JsonObject response = new JsonObject()
|
|
||||||
.put("id", plugin.id);
|
|
||||||
|
|
||||||
return Response.ok(response).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("/plugin")
|
|
||||||
@PATCH
|
|
||||||
public Response editPlugin(JsonObject data) {
|
|
||||||
long id = data.getLong("id");
|
|
||||||
String name = data.getString("name");
|
|
||||||
String description = data.getString("description");
|
|
||||||
|
|
||||||
Plugin plugin = pluginService.editPlugin(id, name, description);
|
|
||||||
|
|
||||||
if (plugin == null)
|
|
||||||
return Response.ok(new JsonObject().put("id", id)).status(Response.Status.NOT_FOUND).build();
|
|
||||||
|
|
||||||
JsonObject response = new JsonObject()
|
|
||||||
.put("id", plugin.id);
|
|
||||||
|
|
||||||
return Response.ok(response).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("/plugin")
|
|
||||||
@DELETE
|
|
||||||
public Response deletePlugin(JsonObject data) {
|
|
||||||
long id = data.getLong("id");
|
|
||||||
|
|
||||||
Plugin plugin = pluginService.deletePlugin(id);
|
|
||||||
|
|
||||||
if (plugin == null)
|
|
||||||
return Response.ok(new JsonObject().put("id", id)).status(Response.Status.NOT_FOUND).build();
|
|
||||||
|
|
||||||
JsonObject response = new JsonObject()
|
|
||||||
.put("id", plugin.id);
|
|
||||||
|
|
||||||
return Response.ok(response).build();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
package eu.m724.mstats.resource.api;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import eu.m724.mstats.orm.Plugin;
|
|
||||||
import eu.m724.mstats.orm.Server;
|
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.ws.rs.Consumes;
|
|
||||||
import jakarta.ws.rs.POST;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import jakarta.ws.rs.core.Response;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Path("/api/server")
|
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
public class ServerApiResource {
|
|
||||||
@Inject
|
|
||||||
ServerService serverService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
SecurityIdentity identity;
|
|
||||||
|
|
||||||
@Path("/boot")
|
|
||||||
@POST
|
|
||||||
public Response boot(BootRequest bootRequest) {
|
|
||||||
BootResponse bootResponse = new BootResponse();
|
|
||||||
Server server;
|
|
||||||
|
|
||||||
if (identity.isAnonymous()) {
|
|
||||||
server = serverService.createServer();
|
|
||||||
bootResponse.token = server.getTokenEncoded();
|
|
||||||
} else {
|
|
||||||
server = identity.getAttribute("server");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> invalidIds = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Long pluginId : bootRequest.plugins) {
|
|
||||||
Plugin plugin = server.addPlugin(pluginId);
|
|
||||||
if (plugin == null)
|
|
||||||
invalidIds.add(pluginId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!invalidIds.isEmpty()) {
|
|
||||||
bootResponse.invalidPlugins = invalidIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverService.
|
|
||||||
|
|
||||||
return Response.ok(bootResponse).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Path("/heartbeat")
|
|
||||||
@POST
|
|
||||||
@RolesAllowed("server")
|
|
||||||
public void heartbeat() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BootRequest {
|
|
||||||
@JsonProperty("plugins")
|
|
||||||
public List<Long> plugins;
|
|
||||||
|
|
||||||
public BootRequest() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BootResponse {
|
|
||||||
@JsonProperty("token")
|
|
||||||
public String token;
|
|
||||||
|
|
||||||
@JsonProperty("invalidPlugins")
|
|
||||||
public List<Long> invalidPlugins;
|
|
||||||
|
|
||||||
public BootResponse() {}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package eu.m724.mstats.resource.api;
|
|
||||||
|
|
||||||
import eu.m724.mstats.orm.Server;
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
|
||||||
public class ServerService {
|
|
||||||
@Transactional
|
|
||||||
public Server createServer() {
|
|
||||||
return Server.createServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void modifyServer(Server server) {
|
|
||||||
server.persistAndFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void heartbeat(Server server, List<Long> plugins) {
|
|
||||||
server.heartbeat =
|
|
||||||
server.persistAndFlush();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue