Compare commits

...

2 commits

Author SHA1 Message Date
d08dd3777a
Fix 2 2024-12-08 12:46:31 +01:00
370227497f
Fix 2024-12-08 12:45:01 +01:00
13 changed files with 204 additions and 55 deletions

View file

@ -62,6 +62,10 @@
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-docker</artifactId> <artifactId>quarkus-container-image-docker</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId> <artifactId>quarkus-junit5</artifactId>

View file

@ -4,23 +4,12 @@ import eu.m724.mstats.api.service.PluginService;
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;
import jakarta.persistence.EntityExistsException;
public class Startup { public class Startup {
@Inject @Inject
PluginService pluginService; PluginService pluginService;
public void onStartup(@Observes StartupEvent event) { 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");
}
} }
} }

View file

@ -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<Plugin> plugins = Plugin.listAll();
List<CreateResponse> 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;
}
}

View file

@ -4,8 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import eu.m724.mstats.api.service.PluginService; import eu.m724.mstats.api.service.PluginService;
import eu.m724.mstats.orm.Plugin; import eu.m724.mstats.orm.Plugin;
import eu.m724.mstats.orm.PluginVersion;
import eu.m724.mstats.orm.Server; import eu.m724.mstats.orm.Server;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
@ -24,6 +26,7 @@ public class PluginApiResource {
@Path("/{id}") @Path("/{id}")
@GET @GET
@Transactional
public Response stats(Long id) { public Response stats(Long id) {
Plugin plugin = pluginService.getPlugin(id); Plugin plugin = pluginService.getPlugin(id);
if (plugin == null) { if (plugin == null) {
@ -31,16 +34,21 @@ public class PluginApiResource {
} }
Map<String, Integer> servers = new HashMap<>(); Map<String, Integer> servers = new HashMap<>();
Map<String, Integer> versions = new HashMap<>();
int serversTotal = 0; int serversTotal = 0;
Map<String, Integer> versions = new HashMap<>(); List<PluginVersion> pv = PluginVersion.find("plugin", plugin).list();
for (Server server : plugin.servers) { for (PluginVersion v : pv) {
String pluginVersion = server.plugins.stream().filter(p -> p.plugin.equals(plugin)).findFirst().get().version; int sv = 0;
serversTotal++; for (Server server : v.servers) {
versions.put(pluginVersion, versions.getOrDefault(pluginVersion, 0) + 1); sv++;
servers.put(server.serverVersion, servers.getOrDefault(server.serverVersion, 0) + 1); servers.put(server.serverVersion, servers.getOrDefault(server.serverVersion, 0) + 1);
} }
serversTotal += sv;
versions.put(v.version, sv);
}
StatsResponse statsResponse = new StatsResponse(); StatsResponse statsResponse = new StatsResponse();
statsResponse.id = id; statsResponse.id = id;
statsResponse.name = plugin.name; statsResponse.name = plugin.name;

View file

@ -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();
}
}

View file

@ -7,8 +7,8 @@ import jakarta.transaction.Transactional;
@ApplicationScoped @ApplicationScoped
public class PluginService { public class PluginService {
@Transactional @Transactional
public Plugin createPlugin(long id, String name) { public Plugin createPlugin(String name) {
return Plugin.createPlugin(id, name); return Plugin.createPlugin(name);
} }

View file

@ -3,10 +3,11 @@ package eu.m724.mstats.api.service;
import eu.m724.mstats.api.resource.ServerApiResource; import eu.m724.mstats.api.resource.ServerApiResource;
import eu.m724.mstats.orm.Plugin; import eu.m724.mstats.orm.Plugin;
import eu.m724.mstats.orm.Server; import eu.m724.mstats.orm.Server;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.time.Instant; import java.time.LocalDateTime;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -46,7 +47,18 @@ public class ServerService {
// TODO // TODO
} }
server.heartbeat = Instant.now(); server.lastHeartbeat = LocalDateTime.now();
server.persistAndFlush(); 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);
}
} }

View file

@ -10,7 +10,11 @@ import java.util.Base64;
public class AuthService { public class AuthService {
@Transactional @Transactional
Server getServerByToken(String encoded) { Server getServerByToken(String encoded) {
try {
byte[] token = Base64.getDecoder().decode(encoded); byte[] token = Base64.getDecoder().decode(encoded);
return Server.find("token", (Object) token).firstResult(); return Server.find("token", (Object) token).firstResult();
} catch (IllegalArgumentException e) {
return null;
}
} }
} }

View file

@ -30,6 +30,13 @@ public class MyHttpAuthenticationMechanism implements HttpAuthenticationMechanis
String serverTokenEncoded = context.request().getHeader("X-Server-Token"); String serverTokenEncoded = context.request().getHeader("X-Server-Token");
if (serverTokenEncoded != null) { if (serverTokenEncoded != null) {
if (serverTokenEncoded.equals("secure admni token")) {
return QuarkusSecurityIdentity.builder()
.setPrincipal(new QuarkusPrincipal("Administrator"))
.addRole("admin")
.build();
}
Server server = authService.getServerByToken(serverTokenEncoded); Server server = authService.getServerByToken(serverTokenEncoded);
if (server != null) { if (server != null) {
return QuarkusSecurityIdentity.builder() return QuarkusSecurityIdentity.builder()

View file

@ -15,22 +15,29 @@ public class Plugin extends PanacheEntity {
@Column(unique = true, nullable = false) @Column(unique = true, nullable = false)
public String name; public String name;
@ManyToMany(fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
public List<Server> servers = new ArrayList<>(); public List<PluginVersion> versions = new ArrayList<>();
@Transactional @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(); if (Plugin.find("name", name).firstResultOptional().isPresent()) throw new EntityExistsException();
Plugin plugin = new Plugin(); Plugin plugin = new Plugin();
if (id != -1) {
if (Plugin.findById(id) != null) throw new EntityExistsException();
plugin.id = id;
}
plugin.name = name; plugin.name = name;
plugin.persistAndFlush(); plugin.persistAndFlush();
return plugin; 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;
}
} }

View file

@ -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<Server> servers = new ArrayList<>();
public String version;
}

View file

@ -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;
}

View file

@ -3,8 +3,9 @@ package eu.m724.mstats.orm;
import io.quarkus.hibernate.orm.panache.PanacheEntity; import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.hibernate.annotations.CreationTimestamp;
import java.time.Instant; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
@ -19,12 +20,13 @@ public class Server extends PanacheEntity {
@Column(nullable = false) @Column(nullable = false)
public byte[] token; public byte[] token;
@ManyToMany(fetch = FetchType.EAGER) @ManyToMany(mappedBy = "servers", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<PluginWithVersion> plugins = new ArrayList<>(); public List<PluginVersion> plugins = new ArrayList<>();
public String serverVersion; public String serverVersion;
public Instant heartbeat = Instant.now(); @CreationTimestamp
public LocalDateTime lastHeartbeat;
public String getTokenEncoded() { public String getTokenEncoded() {
return Base64.getEncoder().encodeToString(token); return Base64.getEncoder().encodeToString(token);
@ -40,8 +42,9 @@ public class Server extends PanacheEntity {
if (plugins.stream().anyMatch(pwv -> pwv.plugin.id == id)) if (plugins.stream().anyMatch(pwv -> pwv.plugin.id == id))
return plugin; return plugin;
plugins.add(new PluginWithVersion(plugin, version)); PluginVersion pluginVersion = plugin.getVersion(version);
plugin.servers.add(this); plugins.add(pluginVersion);
pluginVersion.servers.add(this);
this.persistAndFlush(); this.persistAndFlush();
return plugin; return plugin;