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 @@ - +
@@ -116,7 +139,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()); - } -}