From 77e525b890ace8846644b6fbe4a69cc58e95d40b Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Mon, 29 Jun 2026 03:03:59 +0300 Subject: [PATCH] Refactor card management to support asynchronous operations and improve error handling Signed-off-by: Dmitrii Took 3 minutes --- .github/workflows/workflow.yml | 6 +- .../tech/spmega/client/ChatListener.java | 4 +- .../tech/spmega/client/SPMegaClient.java | 22 +- .../tech/spmega/client/ui/AddCardScreen.java | 4 +- .../tech/spmega/client/ui/CardScreen.java | 32 +- .../tech/spmega/client/ui/PaymentScreen.java | 4 +- .../ui/service/BackendAuthenticator.java | 71 ++-- .../client/ui/service/BankUiService.java | 382 ++++++++++-------- src/main/resources/fabric.mod.json | 14 +- 9 files changed, 279 insertions(+), 260 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 00d0f93..d6e2eb3 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -20,11 +20,11 @@ jobs: dotnet-version: 10.0.x - name: Restore dependencies - run: dotnet restore + run: dotnet restore --project backend/SpMega.Backend/SpMega.Backend.csproj - name: Build - run: dotnet build --no-restore + run: dotnet build --no-restore --project backend/SpMega.Backend/SpMega.Backend.csproj - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build --verbosity normal --project backend/SpMega.Backend/SpMega.Backend.csproj compose: name: Push Docker image to ghcr.io diff --git a/src/client/java/git/yawaflua/tech/spmega/client/ChatListener.java b/src/client/java/git/yawaflua/tech/spmega/client/ChatListener.java index f1d6cec..0816166 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/ChatListener.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/ChatListener.java @@ -9,7 +9,6 @@ import net.minecraft.text.Text; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; public class ChatListener { @@ -57,8 +56,7 @@ public class ChatListener { if (client.player != null) { String playerUuid = client.player.getUuidAsString(); - CompletableFuture - .supplyAsync(() -> BankUiService.instance().addCard(cardId, tokenId, playerUuid)) + BankUiService.instance().addCardAsync(cardId, tokenId, playerUuid) .thenAccept(msg -> { if (client == null) { return; diff --git a/src/client/java/git/yawaflua/tech/spmega/client/SPMegaClient.java b/src/client/java/git/yawaflua/tech/spmega/client/SPMegaClient.java index 4a5e3ea..b1bd754 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/SPMegaClient.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/SPMegaClient.java @@ -26,6 +26,7 @@ import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import org.lwjgl.glfw.GLFW; +import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -166,25 +167,13 @@ public class SPMegaClient implements ClientModInitializer { } UiNotifications.instance().render(drawContext, client.textRenderer, scaledWidth, scaledHeight); }); -// CompletableFuture.supplyAsync(() -> BackendAuthenticator.authenticate(client)) -// .thenAccept(authResult -> { -// if (authResult) { -// System.out.println("SPMega: Authenticated on backend successfully."); -// } else { -// System.err.println("SPMega: Authentication on backend failed"); -// } -// }) -// .exceptionally(ex -> { -// System.err.println("SPMega: Authentication error: " + ex.getMessage()); -// return null; -// }); } ); ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { if (client.player != null) { - BankUiService.instance().refreshOnServerJoin(client.player.getUuidAsString()); + BankUiService.instance().refreshOnServerJoinAsync(client.player.getUuidAsString()); } }); @@ -238,8 +227,13 @@ public class SPMegaClient implements ClientModInitializer { }); new ChatListener().register(); - BackendAuthenticator.authenticate(MinecraftClient.getInstance()); + CompletableFuture.runAsync(() -> BackendAuthenticator.authenticate(MinecraftClient.getInstance())) + .exceptionally(throwable -> { + System.err.println("[SPMEGA] Error during async authenticate: " + throwable.getMessage()); + return null; + }); System.out.println("Author of SPMega make it with 4 cans of monster"); + System.out.println("If u want to see more updates - give me like 10 shekels for monster plzzz"); System.out.println("Initialized beshalom! Tieie tovim!"); } } diff --git a/src/client/java/git/yawaflua/tech/spmega/client/ui/AddCardScreen.java b/src/client/java/git/yawaflua/tech/spmega/client/ui/AddCardScreen.java index 1165a1b..da8cca1 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/ui/AddCardScreen.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/ui/AddCardScreen.java @@ -7,7 +7,6 @@ import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.text.Text; -import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; public class AddCardScreen extends Screen { @@ -85,8 +84,7 @@ public class AddCardScreen extends Screen { addButton.active = false; cancelButton.active = false; - CompletableFuture - .supplyAsync(() -> bankUiService.addCard(cardId, cardToken, playerUuid)) + bankUiService.addCardAsync(cardId, cardToken, playerUuid) .thenAccept(message -> { if (this.client == null) { return; diff --git a/src/client/java/git/yawaflua/tech/spmega/client/ui/CardScreen.java b/src/client/java/git/yawaflua/tech/spmega/client/ui/CardScreen.java index ad2083d..c805537 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/ui/CardScreen.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/ui/CardScreen.java @@ -9,7 +9,6 @@ import net.minecraft.text.Text; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; public class CardScreen extends Screen { private final Screen parent; @@ -46,25 +45,30 @@ public class CardScreen extends Screen { } this.addDrawableChild(ButtonWidget.builder(Text.literal("Удалить"), button -> { - CompletableFuture.supplyAsync(() -> { - bankUiService.removeSelectedCard(); - return true; - }); - notifications.showMessage(bankUiService.getLastMessage()); - this.clearAndInit(); + bankUiService.removeSelectedCardAsync() + .thenAccept(message -> { + if (this.client != null) { + this.client.execute(() -> { + notifications.showMessage(message); + this.clearAndInit(); + }); + } + }); }).dimensions(rightX, startY, columnWidth, 20).build()); this.addDrawableChild(ButtonWidget.builder(Text.literal("Обновить"), button -> { String playerUuid = this.client != null && this.client.player != null ? this.client.player.getUuidAsString() : ""; - CompletableFuture.supplyAsync(() -> { - bankUiService.refreshSelectedCard(playerUuid); - return true; - }); - String message = bankUiService.getLastMessage().isBlank() ? "Карта обновлена" : bankUiService.getLastMessage(); - notifications.showMessage(message); - this.clearAndInit(); + bankUiService.refreshSelectedCardAsync(playerUuid) + .thenAccept(message -> { + if (this.client != null) { + this.client.execute(() -> { + notifications.showMessage(message); + this.clearAndInit(); + }); + } + }); }).dimensions(rightX, startY + 24, columnWidth, 20).build()); this.addDrawableChild(ButtonWidget.builder(Text.literal("Добавить новую"), button -> { diff --git a/src/client/java/git/yawaflua/tech/spmega/client/ui/PaymentScreen.java b/src/client/java/git/yawaflua/tech/spmega/client/ui/PaymentScreen.java index 78129eb..d93a8f2 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/ui/PaymentScreen.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/ui/PaymentScreen.java @@ -12,7 +12,6 @@ import net.minecraft.text.Text; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -382,8 +381,7 @@ public class PaymentScreen extends Screen { } lastRecipientLookup = username; - CompletableFuture - .supplyAsync(() -> bankUiService.loadRecipientCards(username)) + bankUiService.loadRecipientCardsAsync(username) .thenAccept(cards -> { if (this.client == null) { return; diff --git a/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BackendAuthenticator.java b/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BackendAuthenticator.java index 16df42b..5ad58d0 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BackendAuthenticator.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BackendAuthenticator.java @@ -84,12 +84,11 @@ public class BackendAuthenticator { } JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject(); - if (json.has("sessionId")) { - return json.get("sessionId").getAsString(); - } else { + if (!json.has("sessionId")) { System.err.println("[SPMEGA] Invalid response from start endpoint: " + response.body()); throw new IOException("Invalid response from start endpoint: " + response.body()); } + return json.get("sessionId").getAsString(); } private static boolean sendAuthRequestToBackend(UUID uuid, String serverId) throws IOException, InterruptedException { @@ -123,28 +122,27 @@ public class BackendAuthenticator { } JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject(); - if (json.has("token")) { - String token = json.get("token").getAsString(); - if (config != null) { - ModConfig updated = new ModConfig( - config.apiDomain(), - token, - config.allowBackend(), - config.signQuickPayEnabled(), - config.gpsEnabled(), - config.gpsPosition() - ); - SPMega.setConfig(updated); - System.out.println("[SPMEGA] Backend auth successful, saved token."); - return true; - } else { - System.err.println("[SPMEGA] Config is null, cannot save token."); - throw new IOException("Config is null, cannot save token."); - } - } else { + if (!json.has("token")) { System.err.println("[SPMEGA] Invalid response from validate endpoint: " + response.body()); throw new IOException("Invalid response from validate endpoint: " + response.body()); } + if (config == null) { + System.err.println("[SPMEGA] Config is null, cannot save token."); + throw new IOException("Config is null, cannot save token."); + } + + String token = json.get("token").getAsString(); + ModConfig updated = new ModConfig( + config.apiDomain(), + token, + config.allowBackend(), + config.signQuickPayEnabled(), + config.gpsEnabled(), + config.gpsPosition() + ); + SPMega.setConfig(updated); + System.out.println("[SPMEGA] Backend auth successful, saved token."); + return true; } public static void sendCardToBackend(String cardId, String cardToken) { @@ -223,19 +221,13 @@ public class BackendAuthenticator { List cards = new ArrayList<>(); for (com.google.gson.JsonElement el : cardsArray) { JsonObject cardJson = el.getAsJsonObject(); - String cardId = ""; - if (cardJson.has("cardId") && !cardJson.get("cardId").isJsonNull()) { - cardId = cardJson.get("cardId").getAsString(); - } else if (cardJson.has("id") && !cardJson.get("id").isJsonNull()) { - cardId = cardJson.get("id").getAsString(); - } + String cardId = cardJson.has("cardId") && !cardJson.get("cardId").isJsonNull() + ? cardJson.get("cardId").getAsString() + : (cardJson.has("id") && !cardJson.get("id").isJsonNull() ? cardJson.get("id").getAsString() : ""); - String cardToken = ""; - if (cardJson.has("cardToken") && !cardJson.get("cardToken").isJsonNull()) { - cardToken = cardJson.get("cardToken").getAsString(); - } else if (cardJson.has("token") && !cardJson.get("token").isJsonNull()) { - cardToken = cardJson.get("token").getAsString(); - } + String cardToken = cardJson.has("cardToken") && !cardJson.get("cardToken").isJsonNull() + ? cardJson.get("cardToken").getAsString() + : (cardJson.has("token") && !cardJson.get("token").isJsonNull() ? cardJson.get("token").getAsString() : ""); if (!cardId.isEmpty() && !cardToken.isEmpty()) { cards.add(new CardCredentials(cardId, cardToken)); @@ -299,10 +291,10 @@ public class BackendAuthenticator { String url = apiDomain + "/api/v1/transactions"; JsonObject jsonPayload = new JsonObject(); - jsonPayload.addProperty("senderCardId", senderCardId); + jsonPayload.addProperty("cardToUse", senderCardId); jsonPayload.addProperty("cardId", senderCardId); - jsonPayload.addProperty("receiver", receiver); - jsonPayload.addProperty("recipient", receiver); + jsonPayload.addProperty("receiverCard", receiver); + jsonPayload.addProperty("receiverName", receiver); jsonPayload.addProperty("amount", amount); jsonPayload.addProperty("comment", comment); String requestBody = jsonPayload.toString(); @@ -316,11 +308,10 @@ public class BackendAuthenticator { .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200 || response.statusCode() == 201 || response.statusCode() == 204) { - return true; - } else { + if (response.statusCode() != 200 && response.statusCode() != 201 && response.statusCode() != 204) { throw new IOException("Server returned status code " + response.statusCode() + ": " + response.body()); } + return true; } public static List fetchTransactionsFromBackend(String cardId) throws IOException, InterruptedException { diff --git a/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BankUiService.java b/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BankUiService.java index 835d7d5..9b22df2 100644 --- a/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BankUiService.java +++ b/src/client/java/git/yawaflua/tech/spmega/client/ui/service/BankUiService.java @@ -5,6 +5,8 @@ import git.yawaflua.tech.spmega.SPMega; import git.yawaflua.tech.spmega.api.SPWorldsApiClient; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; + import java.util.*; import java.util.concurrent.CompletableFuture; @@ -17,7 +19,7 @@ public final class BankUiService { private final String apiDomain = "https://spworlds.ru"; private int selectedCardIndex; - private String lastMessage = ""; + private volatile String lastMessage = ""; private BankUiService() { this.database = new BankDatabase(FabricLoader.getInstance().getConfigDir().resolve("spmega.db")); @@ -54,11 +56,20 @@ public final class BankUiService { return normalized.substring(normalized.length() - 5); } - public synchronized List getCards() { + private void runOnMainThread(Runnable runnable) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client == null || client.isOnThread()) { + runnable.run(); + } else { + client.execute(runnable); + } + } + + public List getCards() { return Collections.unmodifiableList(cards); } - public synchronized CardViewModel getSelectedCard() { + public CardViewModel getSelectedCard() { if (cards.isEmpty()) { return null; } @@ -66,7 +77,7 @@ public final class BankUiService { return cards.get(selectedCardIndex); } - public synchronized int getSelectedCardIndex() { + public int getSelectedCardIndex() { if (cards.isEmpty()) { return 0; } @@ -74,7 +85,7 @@ public final class BankUiService { return selectedCardIndex; } - public synchronized void setSelectedCardIndex(int index) { + public void setSelectedCardIndex(int index) { if (cards.isEmpty()) { selectedCardIndex = 0; return; @@ -82,7 +93,7 @@ public final class BankUiService { selectedCardIndex = Math.floorMod(index, cards.size()); } - public synchronized CardViewModel cycleSelectedCard(int direction) { + public CardViewModel cycleSelectedCard(int direction) { if (cards.isEmpty()) { return null; } @@ -90,200 +101,219 @@ public final class BankUiService { return cards.get(selectedCardIndex); } - public synchronized String getLastMessage() { + public String getLastMessage() { return lastMessage; } - public synchronized void syncCardsWithBackend(String playerUuid) { - try { - List backendCards = BackendAuthenticator.fetchCardsFromBackend(); - boolean changed = false; - for (CardCredentials creds : backendCards) { - if (database.getCredentials(creds.cardId()) == null) { - database.upsertCardCredentials(creds.cardId(), creds.cardToken()); - refreshCard(creds.cardId(), creds.cardToken(), playerUuid, false, false); - changed = true; + public CompletableFuture syncCardsWithBackendAsync(String playerUuid) { + return CompletableFuture.runAsync(() -> { + try { + List backendCards = BackendAuthenticator.fetchCardsFromBackend(); + boolean changed = false; + for (CardCredentials creds : backendCards) { + if (database.getCredentials(creds.cardId()) == null) { + database.upsertCardCredentials(creds.cardId(), creds.cardToken()); + refreshCardSync(creds.cardId(), creds.cardToken(), playerUuid, false, false); + changed = true; + } } + if (changed) { + reloadCardsFromDb(); + } + } catch (Exception e) { + System.err.println("[SPMEGA] Failed to sync cards with backend: " + e.getMessage()); } - if (changed) { - reloadCardsFromDb(); - } - } catch (Exception e) { - System.err.println("[SPMEGA] Failed to sync cards with backend: " + e.getMessage()); - } + }); } - public synchronized void refreshOnServerJoin(String playerUuid) { - syncCardsWithBackend(playerUuid); + public CompletableFuture refreshOnServerJoinAsync(String playerUuid) { + return CompletableFuture.runAsync(() -> { + try { + List backendCards = BackendAuthenticator.fetchCardsFromBackend(); + for (CardCredentials creds : backendCards) { + if (database.getCredentials(creds.cardId()) == null) { + database.upsertCardCredentials(creds.cardId(), creds.cardToken()); + refreshCardSync(creds.cardId(), creds.cardToken(), playerUuid, false, false); + } + } + } catch (Exception e) { + System.err.println("[SPMEGA] Failed to sync cards with backend: " + e.getMessage()); + } - List storedCards = database.loadCards(); - for (StoredCard card : storedCards) { - refreshCard(card.cardId(), card.cardToken(), playerUuid, false, false); - } - reloadCardsFromDb(); + List storedCards = database.loadCards(); + for (StoredCard card : storedCards) { + refreshCardSync(card.cardId(), card.cardToken(), playerUuid, false, false); + } + reloadCardsFromDb(); + }); } - public synchronized List loadRecipientCards(String username) { + public CompletableFuture> loadRecipientCardsAsync(String username) { CardCredentials credentials = getSelectedCredentials(); if (credentials == null || username == null || username.isBlank()) { - return List.of(); + return CompletableFuture.completedFuture(List.of()); } - try { - List apiCards = apiClient.getPlayerCards(username, toApiAuth(credentials)); - List numbers = new ArrayList<>(); - for (SPWorldsApiClient.PlayerCard apiCard : apiCards) { - if (apiCard.number() != null && !apiCard.number().isBlank()) { - numbers.add(apiCard.name() + " : " + apiCard.number()); + return CompletableFuture.supplyAsync(() -> { + try { + List apiCards = apiClient.getPlayerCards(username, toApiAuth(credentials)); + List numbers = new ArrayList<>(); + for (SPWorldsApiClient.PlayerCard apiCard : apiCards) { + if (apiCard.number() != null && !apiCard.number().isBlank()) { + numbers.add(apiCard.name() + " : " + apiCard.number()); + } } + lastMessage = ""; + return numbers; + } catch (Exception exception) { + lastMessage = "Не удалось получить карты игрока: " + exception.getMessage(); + return List.of(); } - lastMessage = ""; - return numbers; - } catch (Exception exception) { - lastMessage = "Не удалось получить карты игрока: " + exception.getMessage(); - return List.of(); - } + }); } - public synchronized String addCard(String cardIdRaw, String cardTokenRaw, String playerUuid) { + public CompletableFuture addCardAsync(String cardIdRaw, String cardTokenRaw, String playerUuid) { String cardId = cardIdRaw == null ? "" : cardIdRaw.trim(); String cardToken = cardTokenRaw == null ? "" : cardTokenRaw.trim(); if (cardId.isEmpty() || cardToken.isEmpty()) { - lastMessage = "Укажи cardId и cardToken"; - return lastMessage; + return CompletableFuture.completedFuture("Укажи cardId и cardToken"); } try { UUID.fromString(cardId); } catch (IllegalArgumentException exception) { - lastMessage = "cardId должен быть UUID"; + return CompletableFuture.completedFuture("cardId должен быть UUID"); + } + + return CompletableFuture.supplyAsync(() -> { + database.upsertCardCredentials(cardId, cardToken); + boolean refreshed = refreshCardSync(cardId, cardToken, playerUuid, true, true); + if (!refreshed) { + database.deleteCard(cardId); + } + reloadCardsFromDb(); + + if (refreshed && lastMessage.isBlank()) { + return "Карта добавлена"; + } return lastMessage; - } - - database.upsertCardCredentials(cardId, cardToken); - boolean refreshed = refreshCard(cardId, cardToken, playerUuid, true, true); - if (!refreshed) { - database.deleteCard(cardId); - } - reloadCardsFromDb(); - - if (refreshed && lastMessage.isBlank()) { - lastMessage = "Карта добавлена"; - } - return lastMessage; + }); } - public synchronized void removeSelectedCard() { + public CompletableFuture removeSelectedCardAsync() { CardViewModel selected = getSelectedCard(); if (selected == null) { - lastMessage = "Нет карты для удаления"; - return; + return CompletableFuture.completedFuture("Нет карты для удаления"); } String cardId = selected.id(); - database.deleteCard(cardId); - ModConfig config = SPMega.getConfig(); - if (config.allowBackend()) - BackendAuthenticator.deleteCardOnBackend(cardId); - - reloadCardsFromDb(); - if (selectedCardIndex >= cards.size()) { - selectedCardIndex = Math.max(0, cards.size() - 1); - } - lastMessage = "Карта удалена"; - } - - public synchronized void refreshSelectedCard(String playerUuid) { - CardViewModel selected = getSelectedCard(); - if (selected == null) { - lastMessage = "Нет карты для обновления"; - return; - } - - CardCredentials credentials = database.getCredentials(selected.id()); - if (credentials == null) { - lastMessage = "Не найдены креды карты"; - return; - } - - refreshCard(credentials.cardId(), credentials.cardToken(), playerUuid, false, false); - reloadCardsFromDb(); - } - - public synchronized boolean submitPayment(PaymentDraft draft) { - CardCredentials credentials = database.getCredentials(draft.senderCardId()); - if (credentials == null) { - lastMessage = "Не найдены креды карты отправителя"; - return false; - } - - git.yawaflua.tech.spmega.ModConfig config = git.yawaflua.tech.spmega.SPMega.getConfig(); - boolean useBackend = config != null && config.allowBackend(); - - try { - long newBalance = 0; - if (useBackend) { - BackendAuthenticator.createTransactionOnBackend( - draft.senderCardId(), - draft.recipient(), - draft.amount(), - draft.comment() - ); - try { - refreshCard(credentials.cardId(), credentials.cardToken(), "", false, false); - StoredCard updatedCard = database.loadCards().stream() - .filter(c -> c.cardId().equals(credentials.cardId())) - .findFirst() - .orElse(null); - if (updatedCard != null) { - newBalance = updatedCard.balance(); - } - } catch (Exception e) { - System.err.println("[SPMEGA] Failed to refresh card balance after transaction: " + e.getMessage()); - } - } else { - SPWorldsApiClient.TransactionResult result = apiClient.createTransaction( - toApiAuth(credentials), - draft.recipient(), - draft.amount(), - draft.comment() - ); - newBalance = result.balance(); - database.updateCardBalance(credentials.cardId(), newBalance); + return CompletableFuture.supplyAsync(() -> { + database.deleteCard(cardId); + ModConfig config = SPMega.getConfig(); + if (config != null && config.allowBackend()) { + BackendAuthenticator.deleteCardOnBackend(cardId); } - database.insertTransferHistory( - credentials.cardId(), - draft.recipient(), - draft.amount(), - draft.comment(), - newBalance, - "SUCCESS" - ); - reloadCardsFromDb(); - lastMessage = "Перевод выполнен"; - return true; - } catch (Exception exception) { - database.insertTransferHistory( - credentials.cardId(), - draft.recipient(), - draft.amount(), - draft.comment(), - null, - "FAILED: " + trimMessage(exception.getMessage()) - ); - lastMessage = "Ошибка перевода: " + exception.getMessage(); - return false; + runOnMainThread(() -> { + if (selectedCardIndex >= cards.size()) { + selectedCardIndex = Math.max(0, cards.size() - 1); + } + }); + return "Карта удалена"; + }); + } + + public CompletableFuture refreshSelectedCardAsync(String playerUuid) { + CardViewModel selected = getSelectedCard(); + if (selected == null) { + return CompletableFuture.completedFuture("Нет карты для обновления"); } + + return CompletableFuture.supplyAsync(() -> { + CardCredentials credentials = database.getCredentials(selected.id()); + if (credentials == null) { + return "Не найдены креды карты"; + } + + refreshCardSync(credentials.cardId(), credentials.cardToken(), playerUuid, false, false); + reloadCardsFromDb(); + return lastMessage.isBlank() ? "Карта обновлена" : lastMessage; + }); } public CompletableFuture submitPaymentAsync(PaymentDraft draft) { - return CompletableFuture.supplyAsync(() -> submitPayment(draft)); + return CompletableFuture.supplyAsync(() -> { + CardCredentials credentials = database.getCredentials(draft.senderCardId()); + if (credentials == null) { + lastMessage = "Не найдены креды карты отправителя"; + return false; + } + + git.yawaflua.tech.spmega.ModConfig config = git.yawaflua.tech.spmega.SPMega.getConfig(); + boolean useBackend = config != null && config.allowBackend(); + + try { + long newBalance = 0; + if (useBackend) { + BackendAuthenticator.createTransactionOnBackend( + draft.senderCardId(), + draft.recipient(), + draft.amount(), + draft.comment() + ); + try { + refreshCardSync(credentials.cardId(), credentials.cardToken(), "", false, false); + StoredCard updatedCard = database.loadCards().stream() + .filter(c -> c.cardId().equals(credentials.cardId())) + .findFirst() + .orElse(null); + if (updatedCard != null) { + newBalance = updatedCard.balance(); + } + } catch (Exception e) { + System.err.println("[SPMEGA] Failed to refresh card balance after transaction: " + e.getMessage()); + } + } else { + SPWorldsApiClient.TransactionResult result = apiClient.createTransaction( + toApiAuth(credentials), + draft.recipient(), + draft.amount(), + draft.comment() + ); + newBalance = result.balance(); + database.updateCardBalance(credentials.cardId(), newBalance); + } + + database.insertTransferHistory( + credentials.cardId(), + draft.recipient(), + draft.amount(), + draft.comment(), + newBalance, + "SUCCESS" + ); + + reloadCardsFromDb(); + lastMessage = "Перевод выполнен"; + return true; + } catch (Exception exception) { + database.insertTransferHistory( + credentials.cardId(), + draft.recipient(), + draft.amount(), + draft.comment(), + null, + "FAILED: " + trimMessage(exception.getMessage()) + ); + lastMessage = "Ошибка перевода: " + exception.getMessage(); + return false; + } + }); } - private boolean refreshCard( + private boolean refreshCardSync( String cardId, String cardToken, String playerUuid, @@ -319,12 +349,9 @@ public final class BankUiService { } } git.yawaflua.tech.spmega.ModConfig config = git.yawaflua.tech.spmega.SPMega.getConfig(); - if (config.allowBackend()) - CompletableFuture.supplyAsync(() -> { - BackendAuthenticator.sendCardToBackend(cardId, cardToken); - return true; - } - ); + if (config != null && config.allowBackend()) { + BackendAuthenticator.sendCardToBackend(cardId, cardToken); + } lastMessage = ""; return true; @@ -336,22 +363,23 @@ public final class BankUiService { private void reloadCardsFromDb() { List storedCards = database.loadCards(); - cards.clear(); + runOnMainThread(() -> { + cards.clear(); + for (StoredCard stored : storedCards) { + String cardNumber = stored.cardNumber() == null || stored.cardNumber().isBlank() + ? extractLastDigits(stored.cardId()) + : stored.cardNumber(); + String cardName = stored.cardName() == null || stored.cardName().isBlank() ? "Карта" : stored.cardName(); + String title = cardNumber + ": " + cardName; + cards.add(new CardViewModel(stored.cardId(), title, stored.balance())); + } - for (StoredCard stored : storedCards) { - String cardNumber = stored.cardNumber() == null || stored.cardNumber().isBlank() - ? extractLastDigits(stored.cardId()) - : stored.cardNumber(); - String cardName = stored.cardName() == null || stored.cardName().isBlank() ? "Карта" : stored.cardName(); - String title = cardNumber + ": " + cardName; - cards.add(new CardViewModel(stored.cardId(), title, stored.balance())); - } - - if (cards.isEmpty()) { - selectedCardIndex = 0; - } else { - selectedCardIndex = Math.floorMod(selectedCardIndex, cards.size()); - } + if (cards.isEmpty()) { + selectedCardIndex = 0; + } else { + selectedCardIndex = Math.floorMod(selectedCardIndex, cards.size()); + } + }); } private CardCredentials getSelectedCredentials() { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e475690..bb0e8c7 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -3,12 +3,20 @@ "id": "spmega", "version": "${version}", "name": "SPMega", - "description": "yawaflua`s SPRadar+SPMHelper mod, thats make a lot!", + "description": "yawaflua's SPRadar+SPMHelper mod, that's make a lot!", "authors": [ - "Dmitri 'yawaflua' Shimanski aka DOLBAYEB" + { + "name": "Dmitri 'yawaflua' Shimanski aka DOLBAYEB", + "contact": { + "homepage": "https://yawaflua.tech", + "email": "spmega@yawaflua.tech" + } + } ], "contact": { - + "homepage": "https://yawaflua.tech", + "issues": "https://git.yawaflua.tech/spmega", + "sources": "https://git.yawaflua.tech/spmega" }, "license": "CC BY-NC-ND 4.0", "icon": "icon.png",