Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9238069d0e | ||
|
|
8516dce2ee | ||
|
|
7475a4ff32 | ||
|
|
254499b49a | ||
|
|
98bf51eb4c | ||
|
|
5858cc5db0 | ||
|
|
23291a1932 | ||
|
|
9f0209af6c | ||
|
|
0841f51a10 | ||
|
|
77e525b890 |
@@ -20,11 +20,11 @@ jobs:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
run: dotnet restore backend/SpMega.Backend/SpMega.Backend.csproj
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
run: dotnet build --no-restore backend/SpMega.Backend/SpMega.Backend.csproj
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
run: dotnet test --no-build --verbosity normal backend/SpMega.Backend/SpMega.Backend.csproj
|
||||
|
||||
compose:
|
||||
name: Push Docker image to ghcr.io
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
git.yawaflua.tech/yawaflua/spmega
|
||||
|
||||
- name: Build and push API
|
||||
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: backend/
|
||||
file: backend/SpMega.Backend/Dockerfile
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using SpMega.Backend.Persistent.Database;
|
||||
using SpMega.Backend.Persistent.Models.Transactions;
|
||||
using SpMega.Backend.Persistent.Models.Users;
|
||||
@@ -16,6 +18,7 @@ namespace SpMega.Backend.Controllers.v1;
|
||||
[ApiController]
|
||||
public class TransactionsController(AppDbContext context, ILogger<TransactionsController> logger, IConfiguration config) : ControllerBase
|
||||
{
|
||||
private const int LIMIT = 10;
|
||||
private const string BASE_URL = "https://spworlds.ru/api/public/";
|
||||
|
||||
[HttpGet("{billId}")]
|
||||
@@ -127,9 +130,13 @@ public class TransactionsController(AppDbContext context, ILogger<TransactionsCo
|
||||
|
||||
[HttpGet()]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<Transaction>>> GetAllTransactions()
|
||||
public async Task<ActionResult<List<Transaction>>> GetAllTransactions([FromQuery] int p = 1)
|
||||
{
|
||||
return Ok(await context.Transactions.Where(k => k.Sender.Id == ((User)HttpContext.Items["@me"]).Id).ToListAsync());
|
||||
return Ok(await context.Transactions
|
||||
.Where(k => EF.Property<Guid>(k, "SenderId") == ((User)HttpContext.Items["@me"]).Id)
|
||||
.Skip(p - 1 * LIMIT)
|
||||
.Take(p * LIMIT)
|
||||
.ToListAsync());
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
|
||||
@@ -487,10 +487,15 @@
|
||||
<p>Мы собираем и обрабатываем:</p>
|
||||
<ul>
|
||||
<li>Ваш уникальный идентификатор Minecraft (<span class="badge">UUID</span>) и текущий никнейм.</li>
|
||||
<li>Сведения о версии игры, версии Java и используемой версии модификации.</li>
|
||||
<li>Ваш текущий IP-адрес для предотвращения мультиаккаунтинга и выявления попыток взлома API.</li>
|
||||
<li>Время первой и последующих авторизаций.</li>
|
||||
<li>Ваш текущий IP-адрес для предотвращения и выявления попыток взлома API.</li>
|
||||
<li>Техническую информацию о транзакциях (суммы, комментарии, номера виртуальных счетов/карт и идентификаторы).</li>
|
||||
</ul>
|
||||
<p>Мы не сохраняем:</p>
|
||||
<ul>
|
||||
<li>Токен карты «КАК ЕСТЬ», данные шифруются по протоколу AES-256-CBC, и только после этого сохраняются в базе данных.</li>
|
||||
<li>Любые личные данные, кроме указанных выше</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Section 5 -->
|
||||
@@ -501,7 +506,7 @@
|
||||
<ul>
|
||||
<li><strong>Не передавать, не продавать и не распространять</strong> ваши личные данные, IP-адреса и статистику третьим лицам.</li>
|
||||
<li>Использовать собранную анонимизированную статистику исключительно во внутренних аналитических целях для улучшения производительности мода.</li>
|
||||
<li>Раскрывать информацию только в случаях, прямо предусмотренных действующим законодательством при наличии официальных судебных или правоохранительных запросов.</li>
|
||||
<li>Раскрывать информацию только в случаях, прямо предусмотренных действующим законодательством государства Израиль при наличии официальных судебных или правоохранительных запросов.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -517,11 +522,11 @@
|
||||
|
||||
<footer>
|
||||
<div class="footer-links">
|
||||
<a href="/v1/BillPage/00000000-0000-0000-0000-000000000000" class="footer-link">Проверить чек</a>
|
||||
<a href="/00000000" class="footer-link">Проверить чек</a>
|
||||
<span>•</span>
|
||||
<a href="#" class="footer-link">Поддержка</a>
|
||||
<a href="https://t.me/meharmen" class="footer-link">Поддержка</a>
|
||||
</div>
|
||||
<div>© 2026 SPMega. Все права защищены. Minecraft является торговой маркой Mojang Synergies AB.</div>
|
||||
<div>© 2026 Dmitri Shimanski. Все права защищены. NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT..</div>
|
||||
</footer>
|
||||
|
||||
<!-- Scroll to Top Button -->
|
||||
|
||||
@@ -15,12 +15,14 @@ public class Program
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration
|
||||
.AddJsonFile("appsettings.json", false)
|
||||
.AddJsonFile("appsettings.Development.json", false)
|
||||
.AddJsonFile("appsettings.json", true)
|
||||
.AddJsonFile("appsettings.Development.json", true)
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
var encryptionKey = builder.Configuration["Encryption:Key"] ?? "a-default-fallback-only-for-dev-key-change-this!";
|
||||
var conf = builder.Configuration;
|
||||
var encryptionKey = conf["Encryption:Key"] ?? "a-default-fallback-only-for-dev-key-change-this!";
|
||||
EncryptionHelper.Initialize(encryptionKey);
|
||||
_ = conf["JWT:Secret"] ?? conf["JWT__Secret"] ?? throw new InvalidOperationException("JWT__Secret is not configured.");
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddLogging(logging =>
|
||||
|
||||
@@ -47,7 +47,6 @@ public static class EncryptionHelper
|
||||
using var encryptor = aes.CreateEncryptor(aes.Key, iv);
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
// Write the IV to the beginning of the stream so it is stored alongside the ciphertext
|
||||
ms.Write(iv, 0, iv.Length);
|
||||
|
||||
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace SpMega.Backend.Services;
|
||||
public class TokenService(IConfiguration conf)
|
||||
{
|
||||
private const int AccessTokenExpirationMinutes = 24;
|
||||
private readonly string _issuer = conf["JWT__Issuer"] ?? "spmega.il.yawaflua.tech";
|
||||
private readonly string _secret = conf["JWT__Secret"] ?? throw new InvalidOperationException("JWT__Secret is not configured.");
|
||||
private readonly string _issuer = conf["JWT:Issuer"] ?? conf["JWT__Issuer"] ?? "spmega.il.yawaflua.tech";
|
||||
private readonly string _secret = conf["JWT:Secret"] ?? conf["JWT__Secret"] ?? throw new InvalidOperationException("JWT__Secret is not configured.");
|
||||
|
||||
public string GenerateAccessToken(string userName, Guid uuid)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,6 +3,7 @@ package git.yawaflua.tech.spmega.client.ui;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BackendAuthenticator;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankDatabase.LocalTransaction;
|
||||
import git.yawaflua.tech.spmega.client.ui.service.BankUiService;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
@@ -19,6 +20,8 @@ public class TransactionHistoryScreen extends Screen {
|
||||
private final List<LocalTransaction> transactions = new ArrayList<>();
|
||||
private boolean loading = true;
|
||||
private String errorMessage = "";
|
||||
private int scrollOffset = 0;
|
||||
private int currentPage = 1;
|
||||
|
||||
public TransactionHistoryScreen(Screen parent, String cardId, String cardTitle) {
|
||||
super(Text.literal("История транзакций"));
|
||||
@@ -36,40 +39,112 @@ public class TransactionHistoryScreen extends Screen {
|
||||
.dimensions(centerX - 60, startY + 10, 120, 20)
|
||||
.build());
|
||||
|
||||
// Scroll Up/Down buttons
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("▲"), button -> scrollUp())
|
||||
.dimensions(centerX + 140, 65, 20, 20)
|
||||
.build());
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("▼"), button -> scrollDown())
|
||||
.dimensions(centerX + 140, 155, 20, 20)
|
||||
.build());
|
||||
|
||||
// Prev Page button
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("<"), button -> {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
scrollOffset = 0;
|
||||
loadTransactions();
|
||||
}
|
||||
}).dimensions(centerX - 100, startY - 15, 30, 20).build());
|
||||
|
||||
// Next Page button
|
||||
this.addDrawableChild(ButtonWidget.builder(Text.literal(">"), button -> {
|
||||
currentPage++;
|
||||
scrollOffset = 0;
|
||||
loadTransactions();
|
||||
}).dimensions(centerX + 70, startY - 15, 30, 20).build());
|
||||
|
||||
loadTransactions();
|
||||
}
|
||||
|
||||
private void scrollUp() {
|
||||
if (scrollOffset > 0) {
|
||||
scrollOffset--;
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollDown() {
|
||||
if (scrollOffset < Math.max(0, transactions.size() - 6)) {
|
||||
scrollOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
// Support pre-1.20.2 mouse scroll
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
|
||||
if (amount > 0) {
|
||||
scrollUp();
|
||||
} else if (amount < 0) {
|
||||
scrollDown();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Support 1.20.2+ mouse scroll
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
||||
if (verticalAmount > 0) {
|
||||
scrollUp();
|
||||
} else if (verticalAmount < 0) {
|
||||
scrollDown();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void loadTransactions() {
|
||||
loading = true;
|
||||
errorMessage = "";
|
||||
transactions.clear();
|
||||
|
||||
UiNotifications.instance().show(Text.literal("Загрузка..."));
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
System.out.println("[SPMEGA] Transaction history loading started for card: " + cardId + ", page: " + currentPage);
|
||||
List<LocalTransaction> list = null;
|
||||
git.yawaflua.tech.spmega.ModConfig config = git.yawaflua.tech.spmega.SPMega.getConfig();
|
||||
if (config != null && config.allowBackend()) {
|
||||
try {
|
||||
list = BackendAuthenticator.fetchTransactionsFromBackend(cardId);
|
||||
list = BackendAuthenticator.fetchTransactionsFromBackend(cardId, currentPage);
|
||||
System.out.println("[SPMEGA] Fetched " + (list != null ? list.size() : "null") + " transactions from backend.");
|
||||
} catch (Exception e) {
|
||||
System.err.println("[SPMEGA] Failed to fetch transactions from server, falling back to DB: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (list == null) {
|
||||
list = BankUiService.instance().getDatabase().loadTransferHistory(cardId);
|
||||
System.out.println("[SPMEGA] Loaded " + (list != null ? list.size() : "null") + " transactions from database.");
|
||||
}
|
||||
|
||||
List<LocalTransaction> finalList = list;
|
||||
if (this.client != null) {
|
||||
this.client.execute(() -> {
|
||||
List<LocalTransaction> finalList = list != null ? list : new ArrayList<>();
|
||||
MinecraftClient minecraftClient = MinecraftClient.getInstance();
|
||||
if (minecraftClient != null) {
|
||||
minecraftClient.execute(() -> {
|
||||
System.out.println("[SPMEGA] Setting transactions list on main thread. Count: " + finalList.size());
|
||||
this.transactions.addAll(finalList);
|
||||
this.scrollOffset = 0;
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
System.out.println("[SPMEGA] MinecraftClient.getInstance() is null in loadTransactions callback!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (this.client != null) {
|
||||
this.client.execute(() -> {
|
||||
System.err.println("[SPMEGA] Outer exception in loadTransactions: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
MinecraftClient minecraftClient = MinecraftClient.getInstance();
|
||||
if (minecraftClient != null) {
|
||||
minecraftClient.execute(() -> {
|
||||
this.errorMessage = e.getMessage();
|
||||
this.loading = false;
|
||||
});
|
||||
@@ -91,30 +166,38 @@ public class TransactionHistoryScreen extends Screen {
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int startY = 50;
|
||||
int bottomStartY = this.height / 2 + 50;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 20, 0xFFFFFF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Карта: " + cardTitle), centerX, 35, 0xBFBFBF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, centerX, 20, 0xFFFFFFFF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Карта: " + cardTitle), centerX, 35, 0xFFBFBFBF);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Стр. " + currentPage), centerX, bottomStartY - 9, 0xFFFFFFFF);
|
||||
|
||||
if (loading) {
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Загрузка транзакций..."), centerX, this.height / 2 - 10, 0xCCCCCC);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Загрузка транзакций..."), centerX, this.height / 2 - 10, 0xFFCCCCCC);
|
||||
} else if (!errorMessage.isEmpty()) {
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Ошибка: " + errorMessage), centerX, this.height / 2 - 10, 0xFF5555);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Ошибка: " + errorMessage), centerX, this.height / 2 - 10, 0xFFFF5555);
|
||||
} else if (transactions.isEmpty()) {
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Транзакций не найдено"), centerX, this.height / 2 - 10, 0xCCCCCC);
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal("Транзакций не найдено"), centerX, this.height / 2 - 10, 0xFFCCCCCC);
|
||||
} else {
|
||||
int y = startY + 15;
|
||||
for (int i = 0; i < Math.min(6, transactions.size()); i++) {
|
||||
LocalTransaction tx = transactions.get(i);
|
||||
int limit = Math.min(6, transactions.size() - scrollOffset);
|
||||
for (int i = 0; i < limit; i++) {
|
||||
LocalTransaction tx = transactions.get(scrollOffset + i);
|
||||
|
||||
String amountText = tx.amount() + " АР";
|
||||
String dateText = tx.createdAt();
|
||||
if (dateText == null) {
|
||||
dateText = "";
|
||||
}
|
||||
if (dateText.length() > 19) {
|
||||
dateText = dateText.substring(0, 19).replace("T", " ");
|
||||
}
|
||||
String commentText = tx.comment().isEmpty() ? "" : " (" + tx.comment() + ")";
|
||||
|
||||
String line = String.format("%s -> %s | %s%s", dateText, tx.receiver(), amountText, commentText);
|
||||
int color = tx.status().equalsIgnoreCase("SUCCESS") ? 0x55FF55 : 0xFF5555;
|
||||
|
||||
String status = tx.status();
|
||||
int color = (status != null && status.equalsIgnoreCase("SUCCESS")) ? 0xFF55FF55 : 0xFFFF5555;
|
||||
|
||||
context.drawCenteredTextWithShadow(this.textRenderer, Text.literal(line), centerX, y, color);
|
||||
y += 18;
|
||||
|
||||
+108
-50
@@ -1,5 +1,6 @@
|
||||
package git.yawaflua.tech.spmega.client.ui.service;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
@@ -84,12 +85,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 +123,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 +222,13 @@ public class BackendAuthenticator {
|
||||
List<CardCredentials> 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 +292,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,16 +309,17 @@ public class BackendAuthenticator {
|
||||
.build();
|
||||
|
||||
HttpResponse<String> 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<BankDatabase.LocalTransaction> fetchTransactionsFromBackend(String cardId) throws IOException, InterruptedException {
|
||||
public static List<BankDatabase.LocalTransaction> fetchTransactionsFromBackend(String cardId, int page) throws IOException, InterruptedException {
|
||||
System.out.println("[SPMEGA] fetchTransactionsFromBackend called for cardId: " + cardId + ", page: " + page);
|
||||
ModConfig config = SPMega.getConfig();
|
||||
if (config == null || !config.allowBackend()) {
|
||||
System.out.println("[SPMEGA] fetchTransactionsFromBackend aborted: config is null or backend not allowed.");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@@ -337,7 +331,8 @@ public class BackendAuthenticator {
|
||||
apiDomain = apiDomain.substring(0, apiDomain.length() - 1);
|
||||
}
|
||||
|
||||
String url = apiDomain + "/api/v1/transactions?cardId=" + cardId;
|
||||
String url = apiDomain + "/api/v1/transactions?p=" + page;
|
||||
System.out.println("[SPMEGA] Requesting transactions from URL: " + url);
|
||||
|
||||
HttpClient httpClient = HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
|
||||
@@ -347,22 +342,85 @@ public class BackendAuthenticator {
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
System.out.println("[SPMEGA] Response status code: " + response.statusCode());
|
||||
if (response.statusCode() != 200) {
|
||||
System.err.println("[SPMEGA] Failed response body: " + response.body());
|
||||
throw new IOException("Server returned status code " + response.statusCode() + " on fetch transactions.");
|
||||
}
|
||||
|
||||
com.google.gson.JsonArray transactionsArray = JsonParser.parseString(response.body()).getAsJsonArray();
|
||||
String body = response.body();
|
||||
System.out.println("[SPMEGA] Response body: " + (body.length() > 500 ? body.substring(0, 500) + "..." : body));
|
||||
|
||||
JsonElement parsed = JsonParser.parseString(body);
|
||||
com.google.gson.JsonArray transactionsArray = null;
|
||||
|
||||
if (parsed.isJsonArray()) {
|
||||
transactionsArray = parsed.getAsJsonArray();
|
||||
} else if (parsed.isJsonObject()) {
|
||||
JsonObject obj = parsed.getAsJsonObject();
|
||||
if (obj.has("$values") && obj.get("$values").isJsonArray()) {
|
||||
transactionsArray = obj.getAsJsonArray("$values");
|
||||
} else if (obj.has("transactions") && obj.get("transactions").isJsonArray()) {
|
||||
transactionsArray = obj.getAsJsonArray("transactions");
|
||||
} else if (obj.has("values") && obj.get("values").isJsonArray()) {
|
||||
transactionsArray = obj.getAsJsonArray("values");
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionsArray == null) {
|
||||
System.err.println("[SPMEGA] Response body is not a JSON array or wrapped array: " + body);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
System.out.println("[SPMEGA] Total transactions returned from server: " + transactionsArray.size());
|
||||
List<BankDatabase.LocalTransaction> list = new ArrayList<>();
|
||||
for (com.google.gson.JsonElement el : transactionsArray) {
|
||||
JsonObject json = el.getAsJsonObject();
|
||||
String receiver = json.has("receiver") ? json.get("receiver").getAsString() : (json.has("recipient") ? json.get("recipient").getAsString() : "unknown");
|
||||
long amount = json.has("amount") ? json.get("amount").getAsLong() : 0L;
|
||||
String comment = json.has("comment") && !json.get("comment").isJsonNull() ? json.get("comment").getAsString() : "";
|
||||
String status = json.has("status") ? json.get("status").getAsString() : "SUCCESS";
|
||||
String createdAt = json.has("createdAt") ? json.get("createdAt").getAsString() : (json.has("created_at") ? json.get("created_at").getAsString() : "");
|
||||
try {
|
||||
JsonObject json = el.getAsJsonObject();
|
||||
|
||||
list.add(new BankDatabase.LocalTransaction(receiver, amount, comment, status, createdAt));
|
||||
// Check cardId filter
|
||||
String senderCardNumber = "";
|
||||
if (json.has("senderCardNumber") && !json.get("senderCardNumber").isJsonNull()) {
|
||||
senderCardNumber = json.get("senderCardNumber").getAsString();
|
||||
}
|
||||
|
||||
if (!senderCardNumber.equalsIgnoreCase(cardId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String receiver = "unknown";
|
||||
if (json.has("receiver") && !json.get("receiver").isJsonNull()) {
|
||||
receiver = json.get("receiver").getAsString();
|
||||
} else if (json.has("receiverName") && !json.get("receiverName").isJsonNull()) {
|
||||
receiver = json.get("receiverName").getAsString();
|
||||
} else if (json.has("receiverCardNumber") && !json.get("receiverCardNumber").isJsonNull()) {
|
||||
receiver = json.get("receiverCardNumber").getAsString();
|
||||
} else if (json.has("recipient") && !json.get("recipient").isJsonNull()) {
|
||||
receiver = json.get("recipient").getAsString();
|
||||
}
|
||||
|
||||
long amount = json.has("amount") ? json.get("amount").getAsLong() : 0L;
|
||||
String comment = json.has("comment") && !json.get("comment").isJsonNull() ? json.get("comment").getAsString() : "";
|
||||
String status = json.has("status") && !json.get("status").isJsonNull() ? json.get("status").getAsString() : "SUCCESS";
|
||||
|
||||
String createdAt = "";
|
||||
if (json.has("transactionDate") && !json.get("transactionDate").isJsonNull()) {
|
||||
createdAt = json.get("transactionDate").getAsString();
|
||||
} else if (json.has("createdAt") && !json.get("createdAt").isJsonNull()) {
|
||||
createdAt = json.get("createdAt").getAsString();
|
||||
} else if (json.has("created_at") && !json.get("created_at").isJsonNull()) {
|
||||
createdAt = json.get("created_at").getAsString();
|
||||
}
|
||||
|
||||
System.out.printf("[SPMEGA] Parsed transaction: %s -> %s, amount=%d, comment=%s, status=%s%n",
|
||||
createdAt, receiver, amount, comment, status);
|
||||
|
||||
list.add(new BankDatabase.LocalTransaction(receiver, amount, comment, status, createdAt));
|
||||
} catch (Exception parseEx) {
|
||||
System.err.println("[SPMEGA] Error parsing transaction element: " + parseEx.getMessage());
|
||||
}
|
||||
}
|
||||
System.out.println("[SPMEGA] Returning " + list.size() + " filtered transactions for card " + cardId);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CardViewModel> getCards() {
|
||||
private void runOnMainThread(Runnable runnable) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client == null || client.isOnThread()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
client.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
public List<CardViewModel> 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<CardCredentials> 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<Void> syncCardsWithBackendAsync(String playerUuid) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
List<CardCredentials> 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<Void> refreshOnServerJoinAsync(String playerUuid) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
List<CardCredentials> 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<StoredCard> storedCards = database.loadCards();
|
||||
for (StoredCard card : storedCards) {
|
||||
refreshCard(card.cardId(), card.cardToken(), playerUuid, false, false);
|
||||
}
|
||||
reloadCardsFromDb();
|
||||
List<StoredCard> storedCards = database.loadCards();
|
||||
for (StoredCard card : storedCards) {
|
||||
refreshCardSync(card.cardId(), card.cardToken(), playerUuid, false, false);
|
||||
}
|
||||
reloadCardsFromDb();
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized List<String> loadRecipientCards(String username) {
|
||||
public CompletableFuture<List<String>> loadRecipientCardsAsync(String username) {
|
||||
CardCredentials credentials = getSelectedCredentials();
|
||||
if (credentials == null || username == null || username.isBlank()) {
|
||||
return List.of();
|
||||
return CompletableFuture.completedFuture(List.of());
|
||||
}
|
||||
|
||||
try {
|
||||
List<SPWorldsApiClient.PlayerCard> apiCards = apiClient.getPlayerCards(username, toApiAuth(credentials));
|
||||
List<String> 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<SPWorldsApiClient.PlayerCard> apiCards = apiClient.getPlayerCards(username, toApiAuth(credentials));
|
||||
List<String> 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<String> 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<String> 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<String> 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<Boolean> 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<StoredCard> 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() {
|
||||
|
||||
@@ -2,12 +2,12 @@ package git.yawaflua.tech.spmega;
|
||||
|
||||
public record ModConfig(String apiDomain, String apiToken, boolean allowBackend, boolean signQuickPayEnabled,
|
||||
boolean gpsEnabled, GpsHudPosition gpsPosition) {
|
||||
public static final String DEFAULT_API_DOMAIN = "http://localhost:5129";
|
||||
public static final boolean ALLOW_BACKEND = false;
|
||||
public static final String DEFAULT_API_TOKEN = "ulBKE9MWEtIGiPAhXV69I28W9BRiSrV3";
|
||||
public static final String DEFAULT_API_DOMAIN = "https://spmega.yawaflua.tech";
|
||||
public static final boolean ALLOW_BACKEND = true;
|
||||
public static final String DEFAULT_API_TOKEN = "-";
|
||||
public static final boolean DEFAULT_SIGN_QUICK_PAY_ENABLED = true;
|
||||
public static final boolean DEFAULT_GPS_ENABLED = true;
|
||||
public static final GpsHudPosition DEFAULT_GPS_POSITION = GpsHudPosition.TOP_LEFT;
|
||||
public static final GpsHudPosition DEFAULT_GPS_POSITION = GpsHudPosition.TOP_CENTER;
|
||||
|
||||
public static ModConfig createDefault() {
|
||||
return new ModConfig(
|
||||
|
||||
@@ -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",
|
||||
@@ -37,7 +45,9 @@
|
||||
"depends": {
|
||||
"fabricloader": ">=${loader_version}",
|
||||
"fabric-api": "*",
|
||||
"minecraft": "${minecraft_version}",
|
||||
"minecraft": "${minecraft_version}"
|
||||
},
|
||||
"suggests": {
|
||||
"cloth-config2": ">=${cloth_config_version}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user