diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index 22fa14e..bc6a6d5 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -90,7 +90,17 @@
"useHttps": "Use HTTPS",
"confirmDelete": "Delete Restaurant",
"noRestaurants": "No restaurants found. Click \"Add Restaurant\" to create one.",
- "deleteConfirmation": "Are you sure you want to delete this restaurant? This action cannot be undone."
+ "deleteConfirmation": "Are you sure you want to delete this restaurant? This action cannot be undone.",
+ "check": "Check connection",
+ "checkError": "Error",
+ "loadError": "Failed to load restaurants",
+ "createSuccess": "Restaurant created successfully",
+ "updateSuccess": "Restaurant updated successfully",
+ "deleteSuccess": "Restaurant deleted",
+ "httpsUpdateSuccess": "HTTPS status updated",
+ "httpsUpdateError": "Failed to update HTTPS",
+ "passwordRequired": "Password is required for new restaurant",
+ "checkNetworkError": "Network error while checking"
},
"settings": {
"title": "Application Settings",
diff --git a/frontend/src/locales/ru.json b/frontend/src/locales/ru.json
index c89c4c7..0953af7 100644
--- a/frontend/src/locales/ru.json
+++ b/frontend/src/locales/ru.json
@@ -90,7 +90,17 @@
"useHttps": "Использовать HTTPS",
"confirmDelete": "Удалить ресторан",
"noRestaurants": "Ресторанов не найдено. Нажмите \"Добавить ресторан\", чтобы создать его.",
- "deleteConfirmation": "Вы уверены, что хотите удалить этот ресторан? Это действие необратимо."
+ "deleteConfirmation": "Вы уверены, что хотите удалить этот ресторан? Это действие необратимо.",
+ "check": "Проверить подключение",
+ "checkError": "Ошибка",
+ "loadError": "Ошибка загрузки списка ресторанов",
+ "createSuccess": "Ресторан успешно создан",
+ "updateSuccess": "Ресторан успешно обновлён",
+ "deleteSuccess": "Ресторан удалён",
+ "httpsUpdateSuccess": "Статус HTTPS обновлён",
+ "httpsUpdateError": "Не удалось обновить HTTPS",
+ "passwordRequired": "Пароль обязателен для нового ресторана",
+ "checkNetworkError": "Ошибка сети при проверке"
},
"settings": {
"title": "Настройки приложения",
diff --git a/frontend/src/views/Restaurants.vue b/frontend/src/views/Restaurants.vue
index 8c09b8b..9397913 100644
--- a/frontend/src/views/Restaurants.vue
+++ b/frontend/src/views/Restaurants.vue
@@ -43,17 +43,40 @@
{{ rest.login }} |
{{ formatDate(rest.created) }} |
-
-
-
+ |
+
+
+
+
+
+
+
+
+
+ {{ rest.checkResult }}
+
+
|
@@ -64,7 +87,7 @@
-
+
-
+
@@ -132,13 +155,26 @@
{{ t('restaurants.deleteConfirmation') }}
-
+
+
+
+
+
+
+
+
{{ notification.message }}
+
+
@@ -148,16 +184,49 @@ import AppLayout from '../components/Layout/AppLayout.vue';
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
-const restaurants = ref([]);
+
+type Restaurant = {
+ id: number;
+ name: string;
+ host: string;
+ https: boolean;
+ login: string;
+ created: string;
+ checking?: boolean;
+ checkResult?: string | null;
+};
+
+const restaurants = ref([]);
const modalOpen = ref(false);
const modalMode = ref<'create' | 'edit'>('create');
const form = ref({ id: null, name: '', login: '', password: '', host: '', https: false });
const modalTitle = ref('');
const deleteConfirm = ref({ show: false, id: null });
+// Уведомления
+const notification = ref({ show: false, type: 'success' as 'success' | 'error', message: '' });
+
+function showNotification(message: string, type: 'success' | 'error') {
+ notification.value = { show: true, type, message };
+ setTimeout(() => {
+ notification.value.show = false;
+ }, 3000);
+}
+
async function loadRestaurants() {
- const res = await fetch('/api/admin/restaurants');
- restaurants.value = await res.json();
+ try {
+ const res = await fetch('/api/admin/restaurants');
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data = await res.json();
+ restaurants.value = data.map((r: any) => ({
+ ...r,
+ checking: false,
+ checkResult: null
+ }));
+ } catch (e: any) {
+ showNotification(t('restaurants.loadError'), 'error');
+ restaurants.value = [];
+ }
}
function formatDate(dateStr: string) {
@@ -165,21 +234,47 @@ function formatDate(dateStr: string) {
return new Date(dateStr).toLocaleString();
}
-function openModal(mode: 'create' | 'edit', rest: any = null) {
+async function checkRestaurant(rest: Restaurant) {
+ rest.checking = true;
+ rest.checkResult = null; // сбрасываем
+
+ try {
+ const response = await fetch(`/api/admin/restaurants/${rest.id}/check`);
+ const data = await response.json();
+
+ if (data.success) {
+ rest.checkResult = `${data.latency_ms} ms`; // показываем в таблице
+ } else {
+ const errorText = data.error || 'Unknown error';
+ showNotification(`${t('restaurants.checkError')}: ${errorText}`, 'error');
+ // rest.checkResult остаётся null -> ничего не показываем
+ }
+ } catch (error: any) {
+ const msg = t('restaurants.checkNetworkError');
+ showNotification(`${msg}: ${error.message}`, 'error');
+ // rest.checkResult остаётся null
+ } finally {
+ rest.checking = false;
+ }
+}
+
+function openModal(mode: 'create' | 'edit', rest: Restaurant | null = null) {
modalMode.value = mode;
if (mode === 'create') {
form.value = { id: null, name: '', login: '', password: '', host: '', https: false };
modalTitle.value = t('restaurants.add');
} else {
- form.value = {
- id: rest.id,
- name: rest.name,
- login: rest.login,
- password: '',
- host: rest.host,
- https: rest.https || false
- };
- modalTitle.value = t('restaurants.edit');
+ if (rest) {
+ form.value = {
+ id: rest.id,
+ name: rest.name,
+ login: rest.login,
+ password: '',
+ host: rest.host,
+ https: rest.https || false
+ };
+ modalTitle.value = t('restaurants.edit');
+ }
}
modalOpen.value = true;
}
@@ -188,7 +283,7 @@ function closeModal() {
modalOpen.value = false;
}
-async function toggleHttps(rest: any) {
+async function toggleHttps(rest: Restaurant) {
const newHttps = !rest.https;
const payload = {
name: rest.name,
@@ -204,15 +299,22 @@ async function toggleHttps(rest: any) {
});
if (res.ok) {
rest.https = newHttps;
+ showNotification(t('restaurants.httpsUpdateSuccess'), 'success');
} else {
- alert('Failed to update HTTPS status');
+ const errText = await res.text();
+ showNotification(`${t('restaurants.httpsUpdateError')}: ${errText}`, 'error');
}
- } catch (e) {
- alert('Network error');
+ } catch (e: any) {
+ showNotification(`${t('restaurants.httpsUpdateError')}: ${e.message}`, 'error');
}
}
async function submitRestaurant() {
+ if (modalMode.value === 'create' && !form.value.password) {
+ showNotification(t('restaurants.passwordRequired'), 'error');
+ return;
+ }
+
try {
const payload = {
name: form.value.name,
@@ -221,29 +323,28 @@ async function submitRestaurant() {
login: form.value.login,
...(form.value.password ? { password: form.value.password } : {})
};
+ let response;
if (modalMode.value === 'create') {
- if (!form.value.password) {
- alert('Password is required');
- return;
- }
- const res = await fetch('/api/admin/restaurants', {
+ response = await fetch('/api/admin/restaurants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
- if (!res.ok) throw new Error('Create failed');
+ if (!response.ok) throw new Error('Create failed');
+ showNotification(t('restaurants.createSuccess'), 'success');
} else {
- const res = await fetch(`/api/admin/restaurants/${form.value.id}`, {
+ response = await fetch(`/api/admin/restaurants/${form.value.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
- if (!res.ok) throw new Error('Update failed');
+ if (!response.ok) throw new Error('Update failed');
+ showNotification(t('restaurants.updateSuccess'), 'success');
}
await loadRestaurants();
closeModal();
- } catch (e) {
- alert('Operation failed: ' + e.message);
+ } catch (e: any) {
+ showNotification(e.message, 'error');
}
}
@@ -252,15 +353,28 @@ function confirmDelete(id: number) {
}
async function deleteRestaurant(id: number) {
- await fetch(`/api/admin/restaurants/${id}`, { method: 'DELETE' });
- await loadRestaurants();
- deleteConfirm.value.show = false;
+ try {
+ const res = await fetch(`/api/admin/restaurants/${id}`, { method: 'DELETE' });
+ if (!res.ok) throw new Error('Delete failed');
+ showNotification(t('restaurants.deleteSuccess'), 'success');
+ await loadRestaurants();
+ } catch (e: any) {
+ showNotification(e.message, 'error');
+ } finally {
+ deleteConfirm.value.show = false;
+ }
}
onMounted(loadRestaurants);
diff --git a/src/main/java/su/xserver/iikocon/MainVerticle.java b/src/main/java/su/xserver/iikocon/MainVerticle.java
index 4b7238c..fb62911 100644
--- a/src/main/java/su/xserver/iikocon/MainVerticle.java
+++ b/src/main/java/su/xserver/iikocon/MainVerticle.java
@@ -22,6 +22,7 @@ import su.xserver.iikocon.handler.AdminHandler;
import su.xserver.iikocon.handler.AuthHandler;
import su.xserver.iikocon.handler.SecurityHandler;
import su.xserver.iikocon.handler.SetupHandler;
+import su.xserver.iikocon.iiko.IikoOlapClient;
import su.xserver.iikocon.service.*;
import java.util.ArrayList;
@@ -350,6 +351,24 @@ public class MainVerticle extends AbstractVerticle {
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
+ router.get("/api/admin/restaurants/:id/check").handler(rc -> {
+ int id = Integer.parseInt(rc.pathParam("id"));
+ restaurantService.findById(id)
+ .onSuccess(rest -> {
+ if (rest == null) {
+ rc.response().setStatusCode(404).end();
+ } else {
+ IikoOlapClient iiko = new IikoOlapClient(vertx, rest);
+
+ iiko.checkConnection()
+ .onSuccess(res -> rc.response().putHeader("Content-Type", "application/json").end(res.encode()))
+ .onFailure(err -> rc.response().putHeader("Content-Type", "application/json").end(
+ new JsonObject().put("success", false).put("error", err.getMessage()).encode()));
+ }
+ })
+ .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
+ });
+
router.post("/api/admin/restaurants").handler(rc -> {
JsonObject body = rc.body().asJsonObject();
String name = body.getString("name");
diff --git a/src/main/java/su/xserver/iikocon/iiko/IikoOlapClient.java b/src/main/java/su/xserver/iikocon/iiko/IikoOlapClient.java
new file mode 100644
index 0000000..2709384
--- /dev/null
+++ b/src/main/java/su/xserver/iikocon/iiko/IikoOlapClient.java
@@ -0,0 +1,139 @@
+package su.xserver.iikocon.iiko;
+
+import io.vertx.core.Future;
+import io.vertx.core.Promise;
+import io.vertx.core.Vertx;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.client.WebClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+
+public class IikoOlapClient {
+
+ private static final Logger log = LoggerFactory.getLogger(IikoOlapClient.class);
+ private final WebClient webClient;
+ private final String iikoHost;
+ private final String iikoLogin;
+ private final String iikoPassHash;
+
+ public IikoOlapClient(Vertx vertx, String host, String login, String passHash, boolean https) {
+ this.webClient = WebClient.create(vertx);
+ this.iikoHost = (https ? "https://" : "http://") + host + (https ? ":443" : ":80");
+ this.iikoLogin = login;
+ this.iikoPassHash = passHash;
+ }
+
+ public IikoOlapClient(Vertx vertx, JsonObject rest) {
+ this.webClient = WebClient.create(vertx);
+ this.iikoHost = (rest.getBoolean("https") ? "https://" : "http://") + rest.getString("host") + (rest.getBoolean("https") ? ":443" : ":80");
+ this.iikoLogin = rest.getString("login");
+ this.iikoPassHash = rest.getString("password");
+ }
+
+ private Future authenticate() {
+ Promise promise = Promise.promise();
+ String url = iikoHost + "/resto/api/auth"; //?login=" + iikoLogin + "&pass=" + iikoPassHash;
+
+ webClient.getAbs(url)
+ .addQueryParam("login", iikoLogin)
+ .addQueryParam("pass", iikoPassHash)
+ .send()
+ .onSuccess(resp -> {
+ if (resp.statusCode() == 200) {
+ String token = resp.bodyAsString();
+ log.info("Authenticated, token: {}", token);
+ promise.complete(token);
+ } else {
+ promise.fail("Auth failed for " + iikoLogin + ": " + resp.statusCode());
+ }
+ })
+ .onFailure(promise::fail);
+ return promise.future();
+ }
+
+ private Future logout(String token) {
+ if (token == null || token.isEmpty()) {
+ return Future.succeededFuture();
+ }
+ Promise promise = Promise.promise();
+ String url = iikoHost + "/resto/api/logout";
+ webClient.getAbs(url)
+ .addQueryParam("key", token)
+ .send()
+ .onSuccess(resp -> {
+// log.info("Logout completed for token, status {}", resp.statusCode());
+ log.info(resp.bodyAsString());
+ promise.complete();
+ })
+ .onFailure(err -> {
+ log.error("Logout request failed: {}", err.getMessage());
+ promise.complete();
+ });
+ return promise.future();
+ }
+
+ public Future checkConnection() {
+ Promise promise = Promise.promise();
+
+ long time = System.currentTimeMillis();
+
+ authenticate()
+ .onSuccess(token -> {
+ logout(token).mapEmpty();
+ promise.complete(new JsonObject()
+ .put("success", true)
+ .put("latency_ms", System.currentTimeMillis() - time)
+ );
+ })
+ .onFailure(promise::fail);
+ return promise.future();
+ }
+
+ public Future handleGet(String uri, JsonObject params) {
+ Promise promise = Promise.promise();
+
+ authenticate()
+ .onSuccess(token -> {
+ String url = appendQueryParams(iikoHost + uri, params.put("key", token));
+ log.info("Request to : {}", url);
+ webClient.getAbs(url)
+ .send()
+ .onSuccess(resp -> {
+ if (resp.statusCode() == 200) {
+ JsonObject body = resp.bodyAsJsonObject();
+ // Если есть обёртка data, распаковываем
+ JsonObject data = body.containsKey("data") && body.getValue("data") instanceof JsonObject
+ ? body.getJsonObject("data")
+ : body;
+ logout(token).mapEmpty();
+ promise.complete(data);
+ } else {
+ promise.fail("Failed request to " + iikoHost + ": HTTP " + resp.statusCode());
+ }
+ })
+ .onFailure(promise::fail);
+ })
+ .onFailure(promise::fail);
+
+ return promise.future();
+ }
+
+ private String toQueryString(JsonObject params) {
+ if (params == null || params.isEmpty()) {
+ return "";
+ }
+ return "?" + params.stream()
+ .map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "=" +
+ URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8))
+ .collect(Collectors.joining("&"));
+ }
+
+ private String appendQueryParams(String url, JsonObject params) {
+ return url + toQueryString(params);
+ }
+
+}
diff --git a/src/main/java/su/xserver/iikocon/test/IikoOlapColumnsImporter.java b/src/main/java/su/xserver/iikocon/iiko/IikoOlapColumnsImporter.java
similarity index 72%
rename from src/main/java/su/xserver/iikocon/test/IikoOlapColumnsImporter.java
rename to src/main/java/su/xserver/iikocon/iiko/IikoOlapColumnsImporter.java
index 5bb0b59..714b3bb 100644
--- a/src/main/java/su/xserver/iikocon/test/IikoOlapColumnsImporter.java
+++ b/src/main/java/su/xserver/iikocon/iiko/IikoOlapColumnsImporter.java
@@ -1,50 +1,30 @@
-package su.xserver.iikocon.test;
+package su.xserver.iikocon.iiko;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
-import io.vertx.ext.web.client.WebClient;
-import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.mysqlclient.MySQLConnectOptions;
-import io.vertx.sqlclient.*;
+import io.vertx.sqlclient.Pool;
+import io.vertx.sqlclient.PoolOptions;
+import io.vertx.sqlclient.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
-import java.util.HexFormat;
import java.util.List;
public class IikoOlapColumnsImporter {
private static final Logger log = LoggerFactory.getLogger(IikoOlapColumnsImporter.class);
- private final WebClient httpClient;
private final Pool dbPool;
- private final String iikoServer;
- private final String iikoLogin;
- private final String iikoPassword;
- private static long time;
+ private final IikoOlapClient iikoOlapClient;
private static final List REPORT_TYPES = List.of("SALES", "TRANSACTIONS", "DELIVERIES");
- public IikoOlapColumnsImporter(Vertx vertx,
- String iikoServer,
- String iikoLogin,
- String iikoPassword,
- String dbHost, int dbPort,
- String dbName, String dbUser, String dbPassword) {
- WebClientOptions options = new WebClientOptions()
- .setSsl(true)
- .setTrustAll(true)
- .setVerifyHost(false);
- this.httpClient = WebClient.create(vertx, options);
- this.iikoServer = iikoServer;
- this.iikoLogin = iikoLogin;
- this.iikoPassword = iikoPassword;
-
+ public IikoOlapColumnsImporter(Vertx vertx, String iikoServer, String iikoLogin, String iikoPassword, String dbHost, int dbPort, String dbName, String dbUser, String dbPassword) {
+ this.iikoOlapClient = new IikoOlapClient(vertx, iikoServer, iikoLogin, iikoPassword, true);
MySQLConnectOptions connectOptions = new MySQLConnectOptions()
.setHost(dbHost)
.setPort(dbPort)
@@ -56,7 +36,6 @@ public class IikoOlapColumnsImporter {
this.dbPool = Pool.pool(vertx, connectOptions, poolOptions);
}
- // Главный метод: последовательно для каждого reportType делаем auth -> fetch -> store -> logout
public Future fetchAndStoreAll() {
return createTablesIfNotExist()
.compose(v -> processAllReportTypesSequentially())
@@ -65,7 +44,6 @@ public class IikoOlapColumnsImporter {
}
private Future processAllReportTypesSequentially() {
- time = System.currentTimeMillis();
Future result = Future.succeededFuture();
for (String reportType : REPORT_TYPES) {
result = result.compose(v -> processOneReportType(reportType));
@@ -75,91 +53,19 @@ public class IikoOlapColumnsImporter {
private Future processOneReportType(String reportType) {
log.info("Processing report type: {}", reportType);
- return authenticate()
- .compose(token -> {
- return fetchColumnsFromIiko(reportType, token)
- .compose(columnsJson -> storeColumnsToDb(reportType, columnsJson))
- .onComplete(ignored -> logout(token)); // logout всегда, даже при ошибке
- });
- }
-
- // Аутентификация: GET /resto/api/auth?login=...&pass=SHA1
- private Future authenticate() {
- Promise promise = Promise.promise();
- String passHash = sha1(iikoPassword);
- String url = "https://" + iikoServer + ":443/resto/api/auth?login=" + iikoLogin + "&pass=" + passHash;
-
- httpClient.getAbs(url)
- .send()
- .onSuccess(resp -> {
- if (resp.statusCode() == 200) {
- String token = resp.bodyAsString();
- log.info("Authenticated, token: {}", token);
- promise.complete(token);
- } else {
- promise.fail("Auth failed for " + iikoLogin + ": " + resp.statusCode());
- }
- })
- .onFailure(promise::fail);
- return promise.future();
- }
-
- // Logout: GET /resto/api/logout?key=токен
- private Future logout(String token) {
- if (token == null || token.isEmpty()) {
- return Future.succeededFuture();
- }
- Promise promise = Promise.promise();
- String url = "https://" + iikoServer + "/resto/api/logout?key=" + token;
- httpClient.getAbs(url)
- .send()
- .onSuccess(resp -> {
- log.info("Logout completed for token, status {}", resp.statusCode());
- promise.complete();
- })
- .onFailure(err -> {
- log.error("Logout request failed: {}", err.getMessage());
- promise.complete(); // не ломаем цепочку
- });
- return promise.future();
+ return fetchColumnsFromIiko(reportType)
+ .compose(columnsJson -> storeColumnsToDb(reportType, columnsJson));
}
// Запрос полей для конкретного reportType
- private Future fetchColumnsFromIiko(String reportType, String token) {
+ private Future fetchColumnsFromIiko(String reportType) {
Promise promise = Promise.promise();
- String url = "https://" + iikoServer + "/resto/api/v2/reports/olap/columns?key=" + token + "&reportType=" + reportType;
- log.info("Connect to : {}", url);
-
- httpClient.getAbs(url)
- .send()
- .onSuccess(resp -> {
- if (resp.statusCode() == 200) {
- JsonObject body = resp.bodyAsJsonObject();
- // Если есть обёртка data, распаковываем
- JsonObject data = body.containsKey("data") && body.getValue("data") instanceof JsonObject
- ? body.getJsonObject("data")
- : body;
- promise.complete(data);
-
- log.info("time: {}", (System.currentTimeMillis() - time) + "ms");
- } else {
- promise.fail("Failed to fetch columns for " + reportType + ": HTTP " + resp.statusCode());
- }
- })
+ iikoOlapClient.handleGet("/resto/api/v2/reports/olap/columns", new JsonObject().put("reportType", reportType))
+ .onSuccess(promise::complete)
.onFailure(promise::fail);
- return promise.future();
- }
- // SHA-1
- private String sha1(String input) {
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- byte[] digest = md.digest(input.getBytes());
- return HexFormat.of().formatHex(digest).toLowerCase();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
+ return promise.future();
}
// ---------- Методы работы с БД (с префиксом iiko_) ----------
@@ -366,9 +272,4 @@ public class IikoOlapColumnsImporter {
default -> "string";
};
}
-
- public void close() {
- dbPool.close();
- httpClient.close();
- }
}
diff --git a/src/main/java/su/xserver/iikocon/test/Main.java b/src/main/java/su/xserver/iikocon/iiko/Main.java
similarity index 92%
rename from src/main/java/su/xserver/iikocon/test/Main.java
rename to src/main/java/su/xserver/iikocon/iiko/Main.java
index 09a0b40..fa33f2c 100644
--- a/src/main/java/su/xserver/iikocon/test/Main.java
+++ b/src/main/java/su/xserver/iikocon/iiko/Main.java
@@ -1,4 +1,4 @@
-package su.xserver.iikocon.test;
+package su.xserver.iikocon.iiko;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
@@ -16,7 +16,7 @@ public class Main {
vertx,
"folk-amber-co.iiko.it", // без https://
"4444",
- "4444",
+ "92f2fd99879b0c2466ab8648afb63c49032379c1",
"phpmyadmin.xserver.su", // хост MariaDB
3306,
"test", // имя БД
diff --git a/src/main/java/su/xserver/iikocon/service/HealthCheckService.java b/src/main/java/su/xserver/iikocon/service/HealthCheckService.java
index b946e08..4855bac 100644
--- a/src/main/java/su/xserver/iikocon/service/HealthCheckService.java
+++ b/src/main/java/su/xserver/iikocon/service/HealthCheckService.java
@@ -5,6 +5,7 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.healthchecks.Status;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.healthchecks.HealthCheckHandler;
+import su.xserver.iikocon.iiko.IikoOlapClient;
import java.util.Collections;
@@ -55,6 +56,20 @@ public class HealthCheckService {
.onFailure(err -> future.tryFail("DataBase ping failed: " + err.getMessage()));
});
+// healthCheckHandler.register("iiko", future -> {
+//
+// IikoOlapClient iiko = new IikoOlapClient(vertx, "folk-amber-co.iiko.it", "4444", "92f2fd99879b0c2466ab8648afb63c49032379c1", true);
+//
+// iiko.checkConnection()
+// .onSuccess(res -> {
+// JsonObject data = new JsonObject()
+// .put("name", "iiko")
+// .put("latency_ms", res.getLong("latency_ms"));
+// future.complete(Status.OK(data));
+// })
+// .onFailure(err -> future.tryFail("iiko ping failed: " + err.getMessage()));
+// });
+
// Регистрируем endpoint /api/health
router.get("/api/health").handler(healthCheckHandler);
}
diff --git a/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java b/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java
index 77448ef..97e43a3 100644
--- a/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java
+++ b/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java
@@ -6,11 +6,6 @@ import java.time.format.DateTimeParseException;
public class DateRangeSetup {
public static void main(String[] args) {
- // Параметры по умолчанию
- String login = "4444";
- String password = "4444";
- String server = "folk-amber-co.iiko.it";
- String presetId = "7ddc40c3-9d5f-408f-aa1e-652964b36c6c";
// Вычисление dateFrom и dateTo
LocalDate today = LocalDate.now();
@@ -39,11 +34,6 @@ public class DateRangeSetup {
String formattedDateFrom = dateFrom.format(formatter);
String formattedDateTo = dateTo.format(formatter);
- // Вывод переменных (можно заменить на дальнейшее использование)
- System.out.println("login=" + login);
- System.out.println("password=" + password);
- System.out.println("server=" + server);
- System.out.println("presetId=" + presetId);
System.out.println("dateFrom=" + formattedDateFrom);
System.out.println("dateTo=" + formattedDateTo);
}
diff --git a/src/main/java/su/xserver/iikocon/test/IikoOlapClient.java b/src/main/java/su/xserver/iikocon/test/IikoOlapClient.java
deleted file mode 100644
index 466a43a..0000000
--- a/src/main/java/su/xserver/iikocon/test/IikoOlapClient.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package su.xserver.iikocon.test;
-
-import io.vertx.core.Future;
-import io.vertx.core.Promise;
-import io.vertx.core.Vertx;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.web.client.HttpResponse;
-import io.vertx.ext.web.client.WebClient;
-
-public class IikoOlapClient {
-
- private final WebClient webClient;
- private final String baseUrl;
- private final String login;
- private final String password;
-
- // Конструктор клиента
- public IikoOlapClient(Vertx vertx, String baseUrl, String login, String password) {
- this.webClient = WebClient.create(vertx);
- this.baseUrl = baseUrl;
- this.login = login;
- this.password = password;
- }
-
- // Основной метод для получения OLAP-отчета
- public Future getOlapReport(JsonObject reportRequest) {
- Promise promise = Promise.promise();
-
- // 1. Аутентификация
- authenticate()
- .compose(this::getOrganizations) // 2. Получение организаций
- .compose(orgId -> executeReport(reportRequest, orgId)) // 3. Запрос отчета
- .onSuccess(promise::complete)
- .onFailure(promise::fail);
-
- return promise.future();
- }
-
- // Аутентификация и получение токена
- private Future authenticate() {
- Promise promise = Promise.promise();
-
- JsonObject authRequest = new JsonObject()
- .put("login", login)
- .put("password", password);
-
- webClient.post(443, baseUrl, "/resto/api/auth")
- .ssl(true)
- .putHeader("Content-Type", "application/json")
- .sendJson(authRequest)
- .onSuccess(response -> {
- if (response.statusCode() == 200) {
- String token = response.bodyAsJsonObject().getString("token");
- promise.complete(token);
- } else {
- promise.fail("Authentication failed: " + response.statusMessage());
- }
- })
- .onFailure(promise::fail);
-
- return promise.future();
- }
-
- // Получение ID организации (для отчета)
- private Future getOrganizations(String token) {
- Promise promise = Promise.promise();
-
- webClient.get(443, baseUrl, "/resto/api/organizations")
- .ssl(true)
- .putHeader("Authorization", "Bearer " + token)
- .send()
- .onSuccess(response -> {
- if (response.statusCode() == 200) {
- // Берем ID первой организации из списка
- String orgId = response.bodyAsJsonArray()
- .getJsonObject(0)
- .getString("id");
- promise.complete(orgId);
- } else {
- promise.fail("Failed to get organizations: " + response.statusMessage());
- }
- })
- .onFailure(promise::fail);
-
- return promise.future();
- }
-
- // Выполнение запроса OLAP-отчета
- private Future executeReport(JsonObject reportRequest, String organizationId) {
- Promise promise = Promise.promise();
-
- // Добавляем ID организации в тело запроса
- JsonObject fullRequest = reportRequest.copy()
- .put("organizationId", organizationId);
-
- webClient.post(443, baseUrl, "/resto/api/v2/reports/olap")
- .ssl(true)
- .putHeader("Content-Type", "application/json")
- .sendJson(fullRequest)
- .onSuccess(response -> {
- if (response.statusCode() == 200) {
- promise.complete(response.bodyAsJsonObject());
- } else {
- promise.fail("OLAP report request failed: " + response.statusMessage());
- }
- })
- .onFailure(promise::fail);
-
- return promise.future();
- }
-}
diff --git a/src/main/java/su/xserver/iikocon/test/ProxyVerticlev2.java b/src/main/java/su/xserver/iikocon/test/ProxyVerticlev2.java
deleted file mode 100644
index 790c6d2..0000000
--- a/src/main/java/su/xserver/iikocon/test/ProxyVerticlev2.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package su.xserver.iikocon.test;
-
-import io.vertx.core.AbstractVerticle;
-import io.vertx.core.Promise;
-import io.vertx.core.http.HttpMethod;
-import io.vertx.core.json.Json;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.web.Router;
-import io.vertx.ext.web.RoutingContext;
-import io.vertx.ext.web.client.WebClient;
-import io.vertx.ext.web.client.WebClientOptions;
-import io.vertx.ext.web.codec.BodyCodec;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HexFormat;
-
-public class ProxyVerticlev2 extends AbstractVerticle {
-
- private static final Logger log = LoggerFactory.getLogger(ProxyVerticlev2.class);
- private WebClient webClient;
-
- @Override
- public void start(Promise startPromise) {
- webClient = WebClient.create(vertx, new WebClientOptions()
- .setSsl(true)
- .setTrustAll(true)
- .setVerifyHost(false));
-
- Router router = Router.router(vertx);
- router.get("/").handler(this::handleGet);
-
- int port = 80;
- vertx.createHttpServer()
- .requestHandler(router)
- .listen(port).onComplete(http -> {
- if (http.succeeded()) {
- log.info("Proxy server started on port {}", port);
- startPromise.complete();
- } else {
- startPromise.fail(http.cause());
- }
- });
- }
-
- private void handleGet(RoutingContext ctx) {
- String server = "folk-amber-co.iiko.it";
- String password = "4444";
- String login = "4444";
- String reportType = "DELIVERIES";
-
- String signature = sha1(password);
- String authUrl = "https://" + server + ":443/resto/api/auth?login=" + login + "&pass=" + signature;
- webClient.getAbs(authUrl)
- .as(BodyCodec.string())
- .send()
- .onSuccess(authResp -> {
- if (authResp.statusCode() != 200) {
- fail(ctx, authResp.statusCode(), "Authentication failed: " + authResp.statusMessage());
- return;
- }
- String token = authResp.body();
- String dataUrl = "https://" + server + "/resto/api/v2/reports/olap/columns" +
- "?key=" + token + "&reportType=" + reportType;
- log.info("URL: {}", dataUrl);
- webClient.getAbs(dataUrl)
- .as(BodyCodec.jsonObject())
- .send()
- .onSuccess(dataResp -> {
- // logout (fire and forget)
- webClient.getAbs("https://" + server + ":443/resto/api/logout?key=" + token)
- .send()
- .onFailure(err -> log.error("Logout failed: {}", err.getMessage()));
- if (dataResp.statusCode() == 200) {
- JsonObject responseBody = dataResp.body();
- log.info(dataResp.headers().toString());
- Object data = responseBody.getValue("data");
- if (data == null) {
- ctx.response().setStatusCode(200).end(responseBody.encode());
- } else {
- // data может быть массивом, объектом или другим типом
- ctx.response().setStatusCode(200).end(Json.encode(data));
- }
- } else {
- fail(ctx, dataResp.statusCode(), "External API error: " + dataResp.statusMessage());
- }
- })
- .onFailure(err -> fail(ctx, 500, "Data request failed: " + err.getMessage()));
- })
- .onFailure(err -> fail(ctx, 500, "Auth request failed: " + err.getMessage()));
- }
-
- private String sha1(String input) {
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- byte[] digest = md.digest(input.getBytes());
- return HexFormat.of().formatHex(digest).toLowerCase();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
- private void fail(RoutingContext ctx, int status, String message) {
- log.info("Error: {}", message);
- ctx.response().setStatusCode(status).end(new JsonObject().put("error", message).encode());
- }
-}