From 6b2a323a9cd4ed2342a5e6816a747e9078f88efd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Thu, 5 Dec 2024 23:02:15 +0100
Subject: [PATCH] implemented report repository

---
 .../spawn/api/client/HttpRepository.java      |   1 +
 .../spawn/api/client/Repository.java          |   6 +
 .../client/repositories/ReportRepository.java |  41 +++-
 .../repositories/WhitelistRepository.java     |   5 +-
 .../spawn/appliances/report/Report.java       | 214 ++++++------------
 .../appliances/report/ReportsCommand.java     |   2 +-
 6 files changed, 117 insertions(+), 152 deletions(-)

diff --git a/src/main/java/eu/mhsl/craftattack/spawn/api/client/HttpRepository.java b/src/main/java/eu/mhsl/craftattack/spawn/api/client/HttpRepository.java
index 963c3a0..0f66ef4 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/api/client/HttpRepository.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/api/client/HttpRepository.java
@@ -74,6 +74,7 @@ public abstract class HttpRepository extends Repository {
 
     private ReqResp<String> sendHttp(HttpRequest request) {
         try(HttpClient client = HttpClient.newHttpClient()) {
+            this.validateThread();
             HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
             return new ReqResp<>(httpResponse.statusCode(), httpResponse.body());
         } catch(IOException | InterruptedException e) {
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/api/client/Repository.java b/src/main/java/eu/mhsl/craftattack/spawn/api/client/Repository.java
index 9250ece..d51a943 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/api/client/Repository.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/api/client/Repository.java
@@ -1,6 +1,8 @@
 package eu.mhsl.craftattack.spawn.api.client;
 
 import com.google.gson.Gson;
+import eu.mhsl.craftattack.spawn.Main;
+import org.bukkit.Bukkit;
 
 import java.net.URI;
 
@@ -12,4 +14,8 @@ public abstract class Repository {
         this.basePath = basePath;
         this.gson = new Gson();
     }
+    
+    protected void validateThread() {
+        if(Bukkit.isPrimaryThread()) Main.logger().warning("Repository was called synchronously!");
+    }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/ReportRepository.java b/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/ReportRepository.java
index 7da116d..c967e12 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/ReportRepository.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/ReportRepository.java
@@ -2,7 +2,6 @@ package eu.mhsl.craftattack.spawn.api.client.repositories;
 
 import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
 import eu.mhsl.craftattack.spawn.api.client.ReqResp;
-import eu.mhsl.craftattack.spawn.appliances.report.Report;
 import eu.mhsl.craftattack.spawn.util.api.ApiUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -15,22 +14,44 @@ public class ReportRepository extends HttpRepository {
         super(ApiUtil.getBaseUri(), ApiUtil::withAuthorizationSecret);
     }
 
-    public record ReportData(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
+    public record ReportCreationInfo(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
     }
 
-    public record ReportDataResponse(@NotNull String url) {
+    public record ReportUrl(@NotNull String url) {
     }
 
-    public record ReportsResponse(List<ReportInfo> from_self, Object to_self) {
+    public record PlayerReports(
+        List<Report> from_self,
+        Object to_self
+    ) {
+        public record Report(
+            @Nullable Reporter reported,
+            @NotNull String subject,
+            boolean draft,
+            @NotNull String status,
+            @NotNull String url
+        ) {
+            public record Reporter(
+                @NotNull String username,
+                @NotNull String uuid
+            ) {
+            }
+        }
     }
 
-    public record ReportInfo(Reporter reported, @NotNull String subject, boolean draft, @NotNull String status, @NotNull String url) {
+    public ReqResp<PlayerReports> queryReports(UUID player) {
+        return this.get(
+            "report",
+            (parameters) -> parameters.addParameter("uuid", player.toString()),
+            PlayerReports.class
+        );
     }
 
-    public record Reporter(@NotNull String username, @NotNull String uuid) {
-    }
-
-    public ReqResp<ReportsResponse> queryReports(UUID player) {
-        return this.get("reports", (builder) -> builder.addParameter("token", "asd"), input, SendReportResponse.class);
+    public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
+        return this.post(
+            "report",
+            data,
+            ReportUrl.class
+        );
     }
 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/WhitelistRepository.java b/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/WhitelistRepository.java
index f0830b0..414e1b3 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/WhitelistRepository.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/WhitelistRepository.java
@@ -18,12 +18,13 @@ public class WhitelistRepository extends HttpRepository {
         String lastname,
         Long banned_until,
         Long outlawed_until
-    ) {}
+    ) {
+    }
 
     public ReqResp<UserData> getUserData(UUID userId) {
         return this.get(
             "user",
-            uriBuilder -> uriBuilder.addParameter("uuid", userId.toString()),
+            parameters -> parameters.addParameter("uuid", userId.toString()),
             UserData.class
         );
     }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/Report.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/Report.java
index c426c95..d35aac4 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/Report.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/Report.java
@@ -1,7 +1,8 @@
 package eu.mhsl.craftattack.spawn.appliances.report;
 
-import com.google.gson.Gson;
 import eu.mhsl.craftattack.spawn.Main;
+import eu.mhsl.craftattack.spawn.api.client.ReqResp;
+import eu.mhsl.craftattack.spawn.api.client.repositories.ReportRepository;
 import eu.mhsl.craftattack.spawn.appliance.Appliance;
 import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
 import net.kyori.adventure.text.Component;
@@ -10,22 +11,14 @@ import net.kyori.adventure.text.TextComponent;
 import net.kyori.adventure.text.event.ClickEvent;
 import net.kyori.adventure.text.event.HoverEvent;
 import net.kyori.adventure.text.format.NamedTextColor;
-import org.apache.http.client.utils.URIBuilder;
 import org.bukkit.Bukkit;
 import org.bukkit.OfflinePlayer;
 import org.bukkit.entity.Player;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
 import java.util.List;
 import java.util.Optional;
-import java.util.UUID;
 
 public class Report extends Appliance {
     public static Component helpText() {
@@ -38,163 +31,51 @@ public class Report extends Appliance {
             .build();
     }
 
-    private URI apiEndpoint;
-    private String apiSecret;
-
     public Report() {
         super("report");
     }
 
-    private record Request(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
-    }
-
-    private record ReportResponse(@NotNull String url) {
-    }
-
-    private record ReportsResponse(List<ReportInfo> from_self, Object to_self) {
-    }
-
-    private record ReportInfo(Reporter reported, @NotNull String subject, boolean draft, @NotNull String status, @NotNull String url) {
-    }
-
-    private record Reporter(@NotNull String username, @NotNull String uuid) {
-    }
-
     public void reportToUnknown(@NotNull Player issuer) {
-        Request request = new Request(issuer.getUniqueId(), null, "");
-        this.issueReport(issuer, request);
+        ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(issuer.getUniqueId(), null, "");
+        this.createReport(issuer, request);
     }
 
     public void reportToKnown(@NotNull Player issuer, @NotNull String targetUsername, @Nullable String reason) {
         OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(targetUsername);
         if(issuer.getUniqueId().equals(offlinePlayer.getUniqueId())) {
-            issuer.sendMessage(
-                Component.text("Du kannst dich nicht selbst reporten.").color(NamedTextColor.RED)
-            );
+            issuer.sendMessage(Component.text("Du kannst dich nicht selbst reporten.", NamedTextColor.RED));
             return;
         }
 
-        Request request = new Request(issuer.getUniqueId(), offlinePlayer.getUniqueId(), Optional.ofNullable(reason).orElse(""));
-        this.issueReport(issuer, request);
-    }
-
-    public void requestReports(Player issuer) {
-        URIBuilder uriBuilder = new URIBuilder(this.apiEndpoint);
-        uriBuilder.addParameter("secret", apiSecret);
-        uriBuilder.addParameter("uuid", issuer.getUniqueId().toString());
-
-        try(HttpClient client = HttpClient.newHttpClient()) {
-            HttpRequest httpRequest = HttpRequest.newBuilder()
-                .uri(uriBuilder.build())
-                .header("Content-Type", "application/json")
-                .GET()
-                .build();
-
-            HttpResponse<String> httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
-            this.printReports(issuer, httpResponse);
-        } catch (IOException | InterruptedException | URISyntaxException e) {
-            issuer.sendMessage(
-                Component.text("Internal server description: " + e.getMessage()).color(NamedTextColor.RED)
-            );
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void issueReport(Player issuer, Request reportRequest) {
-        URIBuilder uriBuilder = new URIBuilder(this.apiEndpoint);
-        uriBuilder.addParameter("secret", apiSecret);
-
-        try(HttpClient client = HttpClient.newHttpClient()) {
-            HttpRequest httpRequest = HttpRequest.newBuilder()
-                .uri(uriBuilder.build())
-                .header("Content-Type", "application/json")
-                .POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(reportRequest)))
-                .build();
-
-            HttpResponse<String> httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
-            this.printResultMessage(issuer, httpResponse);
-        } catch(IOException | InterruptedException | URISyntaxException e) {
-            issuer.sendMessage(
-                Component.text("Internal server description: " + e.getMessage()).color(NamedTextColor.RED)
-            );
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void printReports(Player issuer, HttpResponse<String> httpResponse) {
-        if(httpResponse.statusCode() != 200) {
-
-            Main.logger().warning("Failed to request Reports: " + httpResponse.statusCode());
-            issuer.sendMessage(
-                Component.text()
-                    .append(Component.text("Interner Serverfehler beim abfragen der Reports.", NamedTextColor.RED))
-                    .appendNewline()
-                    .append(Component.text("Bitte melde dich bei einem Admin!", NamedTextColor.RED))
-            );
-            return;
-        }
-
-        List<ReportInfo> reports = new Gson().fromJson(httpResponse.body(), ReportsResponse.class).from_self;
-        reports.removeIf(reportInfo -> reportInfo.draft);
-        if(reports.isEmpty()) {
-            issuer.sendMessage(
-                Component.text()
-                    .append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
-                    .appendNewline()
-                    .append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
-            );
-            return;
-        }
-
-        ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
-
-        component.append(
-            Component.newline()
-                .append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
-                .appendNewline()
+        ReportRepository.ReportCreationInfo request = new ReportRepository.ReportCreationInfo(
+            issuer.getUniqueId(),
+            offlinePlayer.getUniqueId(),
+            Optional.ofNullable(reason).orElse("")
         );
-
-        reports.forEach(reportInfo -> {
-            component.append(
-                Component.text()
-                    .append(Component.text(" - ", NamedTextColor.WHITE))
-                    .append(Component.text(reportInfo.reported.username, NamedTextColor.WHITE))
-                    .append(Component.text(String.format(": %s", reportInfo.subject), NamedTextColor.GRAY))
-                    .clickEvent(ClickEvent.openUrl(reportInfo.url))
-                    .hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)))
-            );
-            component.appendNewline();
-        });
-
-        issuer.sendMessage(component.build());
+        this.createReport(issuer, request);
     }
 
-    private void printResultMessage(Player issuer, HttpResponse<String> httpResponse) {
-        switch(httpResponse.statusCode()) {
+    private void createReport(Player issuer, ReportRepository.ReportCreationInfo reportRequest) {
+        ReqResp<ReportRepository.ReportUrl> createdReport = this.queryRepository(ReportRepository.class)
+            .createReport(reportRequest);
+
+        switch(createdReport.status()) {
             case 201:
-                ReportResponse createdReport = new Gson().fromJson(httpResponse.body(), ReportResponse.class);
                 issuer.sendMessage(
                     Component.text()
                         .append(Component.text("\\/".repeat(20), NamedTextColor.DARK_GRAY))
                         .appendNewline()
-                        .appendNewline()
-                        .append(Component.text("!!! Report hier fertigstellen !!!", NamedTextColor.GOLD))
+                        .append(Component.text("⚠ Der Report muss über den folgenden Link fertiggestellt werden!", NamedTextColor.GOLD))
                         .appendNewline()
                         .appendNewline()
                         .append(
                             Component
-                                .text(" > Hier klicken < ", NamedTextColor.GREEN)
-                                .hoverEvent(HoverEvent.showText(Component.text(createdReport.url).color(NamedTextColor.GREEN)))
-                                .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.url))
+                                .text(createdReport.data().url(), NamedTextColor.GRAY) // URL mit Weltkugel-Emoji
+                                .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.data().url()))
                         )
                         .appendNewline()
                         .appendNewline()
-                        .append(
-                            Component
-                                .text(createdReport.url, NamedTextColor.GRAY)
-                                .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, createdReport.url))
-                        )
-                        .appendNewline()
+                        .append(Component.text("Ohne das Fertigstellen des Reports wird dieser nicht bearbeitet!", NamedTextColor.DARK_RED))
                         .appendNewline()
                         .append(Component.text("/\\".repeat(20), NamedTextColor.DARK_GRAY))
                 );
@@ -213,7 +94,7 @@ public class Report extends Appliance {
 
             case 401:
             default:
-                Main.logger().warning("Failed to request Report: " + httpResponse.statusCode());
+                Main.logger().warning("Failed to request Report: " + createdReport.status());
                 issuer.sendMessage(
                     Component.text()
                         .append(Component.text("Interner Serverfehler beim anlegen des Reports.", NamedTextColor.RED))
@@ -224,6 +105,61 @@ public class Report extends Appliance {
         }
     }
 
+    public void queryReports(Player issuer) {
+        ReqResp<ReportRepository.PlayerReports> userReports = this.queryRepository(ReportRepository.class)
+            .queryReports(issuer.getUniqueId());
+
+        if(userReports.status() != 200) {
+            Main.logger().warning("Failed to request Reports: " + userReports.status());
+            issuer.sendMessage(
+                Component.text()
+                    .append(Component.text("Interner Serverfehler beim abfragen der Reports.", NamedTextColor.RED))
+                    .appendNewline()
+                    .append(Component.text("Bitte melde dich bei einem Admin!", NamedTextColor.RED))
+            );
+            return;
+        }
+
+        List<ReportRepository.PlayerReports.Report> reports = userReports
+            .data()
+            .from_self()
+            .stream()
+            .filter(report -> !report.draft())
+            .toList()
+            .reversed();
+
+        if(reports.isEmpty()) {
+            issuer.sendMessage(
+                Component.text()
+                    .append(Component.text("Du hast noch niemanden reportet.", NamedTextColor.RED))
+                    .appendNewline()
+                    .append(Component.text("Um jemanden zu melden, nutze /report", NamedTextColor.GRAY))
+            );
+            return;
+        }
+
+        ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text()
+            .append(Component.newline())
+            .append(Component.text("Von dir erstellte Reports: ", NamedTextColor.GOLD))
+            .appendNewline();
+
+        reports.forEach(report -> {
+            component
+                .append(Component.text(" - ", NamedTextColor.WHITE))
+                .append(
+                    report.reported() != null
+                        ? Component.text(report.reported().username(), NamedTextColor.WHITE)
+                        : Component.text("Unbekannt", NamedTextColor.YELLOW)
+                )
+                .append(Component.text(String.format(": %s", report.subject()), NamedTextColor.GRAY))
+                .clickEvent(ClickEvent.openUrl(report.url()))
+                .hoverEvent(HoverEvent.showText(Component.text("Klicke, um den Report einzusehen.", NamedTextColor.GOLD)));
+            component.appendNewline();
+        });
+
+        issuer.sendMessage(component.build());
+    }
+
     @Override
     @NotNull
     protected List<ApplianceCommand<?>> commands() {
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/ReportsCommand.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/ReportsCommand.java
index 6000b83..88d5dea 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/ReportsCommand.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/report/ReportsCommand.java
@@ -14,6 +14,6 @@ public class ReportsCommand extends ApplianceCommand.PlayerChecked<Report> {
     @Override
     protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
         sender.sendMessage(ComponentUtil.pleaseWait());
-        getAppliance().requestReports(getPlayer());
+        getAppliance().queryReports(getPlayer());
     }
 }