diff --git a/pom.xml b/pom.xml index f6ebb51..6df96da 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,10 @@ io.quarkus quarkus-container-image-docker + + io.quarkus + quarkus-scheduler + io.quarkus quarkus-junit5 diff --git a/src/main/java/eu/m724/mstats/Startup.java b/src/main/java/eu/m724/mstats/Startup.java index 555c53e..a88c78a 100644 --- a/src/main/java/eu/m724/mstats/Startup.java +++ b/src/main/java/eu/m724/mstats/Startup.java @@ -4,23 +4,12 @@ import eu.m724.mstats.api.service.PluginService; import io.quarkus.runtime.StartupEvent; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; -import jakarta.persistence.EntityExistsException; public class Startup { @Inject PluginService pluginService; public void onStartup(@Observes StartupEvent event) { - try { - pluginService.createPlugin(1, "Tweaks724"); - } catch (EntityExistsException e) { - System.out.println("exists1"); - } - try { - pluginService.createPlugin(2, "Giants"); - } catch (EntityExistsException e) { - System.out.println("exists2"); - } } } diff --git a/src/main/java/eu/m724/mstats/api/resource/AdminApiResource.java b/src/main/java/eu/m724/mstats/api/resource/AdminApiResource.java new file mode 100644 index 0000000..a89f627 --- /dev/null +++ b/src/main/java/eu/m724/mstats/api/resource/AdminApiResource.java @@ -0,0 +1,92 @@ +package eu.m724.mstats.api.resource; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import eu.m724.mstats.api.service.AdminService; +import eu.m724.mstats.orm.Plugin; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.persistence.EntityExistsException; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import java.util.ArrayList; +import java.util.List; + +@Path("/api/admin") +@RolesAllowed("admin") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class AdminApiResource { + @Inject + AdminService adminService; + + @Path("/plugin") + @PUT + public Response createPlugin(CreateRequest request) { + Plugin plugin; + Response.Status status = Response.Status.OK; + try { + plugin = Plugin.createPlugin(request.name); + } catch (EntityExistsException e) { + plugin = Plugin.find("name", request.name).firstResult(); + status = Response.Status.CONFLICT; + } + + var response = new CreateResponse(plugin.id, plugin.name); + + return Response.status(status).entity(response).build(); + } + + @Path("/plugin") + @DELETE + public Response deletePlugin(DeleteRequest request) { + Plugin plugin = Plugin.findById(request.id); + + if (plugin == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + var response = new CreateResponse(plugin.id, plugin.name); + + adminService.deletePlugin(plugin); + + return Response.ok(response).build(); + } + + @Path("/plugins") + @GET + public Response getPlugins() { + List plugins = Plugin.listAll(); + + List responses = new ArrayList<>(); + + plugins.forEach(v -> responses.add(new CreateResponse(v.id, v.name))); + + return Response.ok(responses).build(); + } + + static class DeleteRequest { + @JsonProperty("id") + public long id; + } + + static class CreateRequest { + @JsonProperty("name") + public String name; + } + + static class CreateResponse { + CreateResponse(long id, String name) { + this.id = id; + this.name = name; + } + + @JsonProperty("id") + public long id; + + @JsonProperty("name") + public String name; + } +} diff --git a/src/main/java/eu/m724/mstats/api/resource/PluginApiResource.java b/src/main/java/eu/m724/mstats/api/resource/PluginApiResource.java index ef10fa1..373e81a 100644 --- a/src/main/java/eu/m724/mstats/api/resource/PluginApiResource.java +++ b/src/main/java/eu/m724/mstats/api/resource/PluginApiResource.java @@ -4,8 +4,10 @@ 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.PluginVersion; import eu.m724.mstats.orm.Server; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; @@ -24,6 +26,7 @@ public class PluginApiResource { @Path("/{id}") @GET + @Transactional public Response stats(Long id) { Plugin plugin = pluginService.getPlugin(id); if (plugin == null) { @@ -31,14 +34,19 @@ public class PluginApiResource { } Map servers = new HashMap<>(); + Map versions = new HashMap<>(); int serversTotal = 0; - Map 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); + List pv = PluginVersion.find("plugin", plugin).list(); + for (PluginVersion v : pv) { + int sv = 0; + for (Server server : v.servers) { + sv++; + servers.put(server.serverVersion, servers.getOrDefault(server.serverVersion, 0) + 1); + } + + serversTotal += sv; + versions.put(v.version, sv); } StatsResponse statsResponse = new StatsResponse(); diff --git a/src/main/java/eu/m724/mstats/api/service/AdminService.java b/src/main/java/eu/m724/mstats/api/service/AdminService.java new file mode 100644 index 0000000..1e9e551 --- /dev/null +++ b/src/main/java/eu/m724/mstats/api/service/AdminService.java @@ -0,0 +1,13 @@ +package eu.m724.mstats.api.service; + +import eu.m724.mstats.orm.Plugin; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; + +@ApplicationScoped +public class AdminService { + @Transactional + public void deletePlugin(Plugin plugin) { + plugin.delete(); + } +} diff --git a/src/main/java/eu/m724/mstats/api/service/PluginService.java b/src/main/java/eu/m724/mstats/api/service/PluginService.java index 406a7ae..6518786 100644 --- a/src/main/java/eu/m724/mstats/api/service/PluginService.java +++ b/src/main/java/eu/m724/mstats/api/service/PluginService.java @@ -7,8 +7,8 @@ import jakarta.transaction.Transactional; @ApplicationScoped public class PluginService { @Transactional - public Plugin createPlugin(long id, String name) { - return Plugin.createPlugin(id, name); + public Plugin createPlugin(String name) { + return Plugin.createPlugin(name); } diff --git a/src/main/java/eu/m724/mstats/api/service/ServerService.java b/src/main/java/eu/m724/mstats/api/service/ServerService.java index 2ae0993..60a37e7 100644 --- a/src/main/java/eu/m724/mstats/api/service/ServerService.java +++ b/src/main/java/eu/m724/mstats/api/service/ServerService.java @@ -3,10 +3,11 @@ package eu.m724.mstats.api.service; import eu.m724.mstats.api.resource.ServerApiResource; import eu.m724.mstats.orm.Plugin; import eu.m724.mstats.orm.Server; +import io.quarkus.scheduler.Scheduled; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; -import java.time.Instant; +import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; @@ -46,7 +47,18 @@ public class ServerService { // TODO } - server.heartbeat = Instant.now(); + server.lastHeartbeat = LocalDateTime.now(); server.persistAndFlush(); } + + @Transactional + @Scheduled(every="30m") + public void cleanUp() { + System.out.println("Cleanup running"); + + LocalDateTime now = LocalDateTime.now().minusHours(2); + long deleted = Server.delete("lastHeartbeat < ?1", now); + + System.out.printf("Deleted %d servers\n", deleted); + } } diff --git a/src/main/java/eu/m724/mstats/auth/AuthService.java b/src/main/java/eu/m724/mstats/auth/AuthService.java index 4e32458..25f04c9 100644 --- a/src/main/java/eu/m724/mstats/auth/AuthService.java +++ b/src/main/java/eu/m724/mstats/auth/AuthService.java @@ -10,7 +10,11 @@ import java.util.Base64; public class AuthService { @Transactional Server getServerByToken(String encoded) { - byte[] token = Base64.getDecoder().decode(encoded); - return Server.find("token", (Object) token).firstResult(); + try { + byte[] token = Base64.getDecoder().decode(encoded); + return Server.find("token", (Object) token).firstResult(); + } catch (IllegalArgumentException e) { + return null; + } } } diff --git a/src/main/java/eu/m724/mstats/auth/MyHttpAuthenticationMechanism.java b/src/main/java/eu/m724/mstats/auth/MyHttpAuthenticationMechanism.java index f4588c7..cf38b60 100644 --- a/src/main/java/eu/m724/mstats/auth/MyHttpAuthenticationMechanism.java +++ b/src/main/java/eu/m724/mstats/auth/MyHttpAuthenticationMechanism.java @@ -27,9 +27,16 @@ public class MyHttpAuthenticationMechanism implements HttpAuthenticationMechanis @Override public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) { return Uni.createFrom().item((Supplier) () -> { - String serverTokenEncoded = context.request().getHeader("X-Server-Token"); + String serverTokenEncoded = context.request().getHeader("Server-Token"); if (serverTokenEncoded != null) { + if (serverTokenEncoded.equals("iAdmin")) { + return QuarkusSecurityIdentity.builder() + .setPrincipal(new QuarkusPrincipal("Administrator")) + .addRole("admin") + .build(); + } + Server server = authService.getServerByToken(serverTokenEncoded); if (server != null) { return QuarkusSecurityIdentity.builder() diff --git a/src/main/java/eu/m724/mstats/orm/Plugin.java b/src/main/java/eu/m724/mstats/orm/Plugin.java index dc1c0a3..0cba8fe 100644 --- a/src/main/java/eu/m724/mstats/orm/Plugin.java +++ b/src/main/java/eu/m724/mstats/orm/Plugin.java @@ -15,22 +15,29 @@ public class Plugin extends PanacheEntity { @Column(unique = true, nullable = false) public String name; - @ManyToMany(fetch = FetchType.EAGER) - public List servers = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + public List versions = new ArrayList<>(); @Transactional - public static Plugin createPlugin(long id, String name) { + public static Plugin createPlugin(String name) { if (Plugin.find("name", name).firstResultOptional().isPresent()) throw new EntityExistsException(); Plugin plugin = new Plugin(); - if (id != -1) { - if (Plugin.findById(id) != null) throw new EntityExistsException(); - plugin.id = id; - } plugin.name = name; plugin.persistAndFlush(); return plugin; } + + @Transactional + public PluginVersion getVersion(String version) { + PluginVersion pv = PluginVersion.find("plugin = ?1 and version = ?2", this, version).firstResult(); + if (pv == null) { + pv = new PluginVersion(this, version); + pv.persistAndFlush(); + } + + return pv; + } } diff --git a/src/main/java/eu/m724/mstats/orm/PluginVersion.java b/src/main/java/eu/m724/mstats/orm/PluginVersion.java new file mode 100644 index 0000000..01d9184 --- /dev/null +++ b/src/main/java/eu/m724/mstats/orm/PluginVersion.java @@ -0,0 +1,29 @@ +package eu.m724.mstats.orm; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table( + uniqueConstraints = @UniqueConstraint(columnNames = { "plugin", "version" }), + indexes = @Index(columnList = "plugin_id") +) +public class PluginVersion extends PanacheEntity { + public PluginVersion() {} + + public PluginVersion(Plugin plugin, String version) { + this.plugin = plugin; + this.version = version; + } + + @ManyToOne + public Plugin plugin; + + @ManyToMany + public List servers = new ArrayList<>(); + + public String version; +} diff --git a/src/main/java/eu/m724/mstats/orm/PluginWithVersion.java b/src/main/java/eu/m724/mstats/orm/PluginWithVersion.java deleted file mode 100644 index cd893ce..0000000 --- a/src/main/java/eu/m724/mstats/orm/PluginWithVersion.java +++ /dev/null @@ -1,19 +0,0 @@ -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; -} diff --git a/src/main/java/eu/m724/mstats/orm/Server.java b/src/main/java/eu/m724/mstats/orm/Server.java index f221f19..6034995 100644 --- a/src/main/java/eu/m724/mstats/orm/Server.java +++ b/src/main/java/eu/m724/mstats/orm/Server.java @@ -3,8 +3,9 @@ package eu.m724.mstats.orm; import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import jakarta.transaction.Transactional; +import org.hibernate.annotations.CreationTimestamp; -import java.time.Instant; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -19,12 +20,13 @@ public class Server extends PanacheEntity { @Column(nullable = false) public byte[] token; - @ManyToMany(fetch = FetchType.EAGER) - public List plugins = new ArrayList<>(); + @ManyToMany(mappedBy = "servers", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + public List plugins = new ArrayList<>(); public String serverVersion; - public Instant heartbeat = Instant.now(); + @CreationTimestamp + public LocalDateTime lastHeartbeat; public String getTokenEncoded() { return Base64.getEncoder().encodeToString(token); @@ -40,8 +42,9 @@ public class Server extends PanacheEntity { if (plugins.stream().anyMatch(pwv -> pwv.plugin.id == id)) return plugin; - plugins.add(new PluginWithVersion(plugin, version)); - plugin.servers.add(this); + PluginVersion pluginVersion = plugin.getVersion(version); + plugins.add(pluginVersion); + pluginVersion.servers.add(this); this.persistAndFlush(); return plugin;