This commit is contained in:
2026-04-18 11:33:21 +03:00
parent c4e113a494
commit af757ff224
11 changed files with 753 additions and 27 deletions

View File

@@ -0,0 +1,50 @@
package su.xserver.iikocon;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
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();
LocalDate dateFrom = today.minusDays(7);
LocalDate dateTo = today;
// Переопределение из аргументов командной строки
if (args.length > 0 && args[0] != null && !args[0].isEmpty()) {
try {
dateFrom = LocalDate.parse(args[0]);
} catch (DateTimeParseException e) {
System.err.println("Ошибка парсинга dateFrom: " + args[0] + ". Используется значение по умолчанию.");
}
}
if (args.length > 1 && args[1] != null && !args[1].isEmpty()) {
try {
dateTo = LocalDate.parse(args[1]);
} catch (DateTimeParseException e) {
System.err.println("Ошибка парсинга dateTo: " + args[1] + ". Используется значение по умолчанию.");
}
}
// Форматирование дат в YYYY-MM-DD
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
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);
}
}

View File

@@ -156,6 +156,42 @@ public class MainVerticle extends AbstractVerticle {
}
}));
router.post("/api/admin/users").handler(rc -> {
JsonObject body = rc.body().asJsonObject();
String login = body.getString("login");
String password = body.getString("password");
String ip = rc.request().remoteAddress().host();
if (login == null || password == null) {
rc.response().setStatusCode(400).end("Missing login or password");
return;
}
userService.createUser(login, password, ip)
.onSuccess(v -> rc.response().setStatusCode(201).end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
router.put("/api/admin/users/:id").handler(rc -> {
int id = Integer.parseInt(rc.pathParam("id"));
JsonObject body = rc.body().asJsonObject();
String login = body.getString("login");
String password = body.getString("password");
String ip = rc.request().remoteAddress().host();
if (login == null) {
rc.response().setStatusCode(400).end("Missing login");
return;
}
userService.updateUser(id, login, password, ip)
.onSuccess(v -> rc.response().end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
router.delete("/api/admin/users/:id").handler(rc -> {
int id = Integer.parseInt(rc.pathParam("id"));
userService.deleteUser(id)
.onSuccess(v -> rc.response().end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
// Получение текущего пользователя
router.get("/api/admin/me").handler(rc -> {
Integer userId = rc.session().get("userId");
@@ -171,6 +207,64 @@ public class MainVerticle extends AbstractVerticle {
}
});
router.get("/api/admin/restaurants").handler(rc -> restaurantService.getAllRestaurants().onComplete(ar -> {
if (ar.succeeded()) {
rc.response()
.putHeader("Content-Type", "application/json")
.end(ar.result().encode());
} else {
rc.response().setStatusCode(500).end(ar.cause().getMessage());
}
}));
router.get("/api/admin/restaurants/:id").handler(rc -> {
int id = Integer.parseInt(rc.pathParam("id"));
restaurantService.findById(id)
.onSuccess(rest -> {
if (rest == null) rc.response().setStatusCode(404).end();
else rc.response().putHeader("Content-Type", "application/json").end(rest.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");
String login = body.getString("login");
String password = body.getString("password");
String host = body.getString("host");
if (name == null || login == null || password == null || host == null) {
rc.response().setStatusCode(400).end("Missing fields");
return;
}
restaurantService.createRestaurant(name, login, password, host)
.onSuccess(v -> rc.response().setStatusCode(201).end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
router.put("/api/admin/restaurants/:id").handler(rc -> {
int id = Integer.parseInt(rc.pathParam("id"));
JsonObject body = rc.body().asJsonObject();
String name = body.getString("name");
String login = body.getString("login");
String password = body.getString("password");
String host = body.getString("host");
if (name == null || login == null || host == null) {
rc.response().setStatusCode(400).end("Missing required fields");
return;
}
restaurantService.updateRestaurant(id, name, login, password, host)
.onSuccess(v -> rc.response().end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
router.delete("/api/admin/restaurants/:id").handler(rc -> {
int id = Integer.parseInt(rc.pathParam("id"));
restaurantService.deleteRestaurant(id)
.onSuccess(v -> rc.response().end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
return router;
}

View File

@@ -0,0 +1,194 @@
package su.xserver.iikocon;
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 java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public class ProxyVerticle extends AbstractVerticle {
private WebClient webClient;
@Override
public void start(Promise<Void> startPromise) {
webClient = WebClient.create(vertx, new WebClientOptions()
.setSsl(true)
.setTrustAll(true)
.setVerifyHost(false));
Router router = Router.router(vertx);
router.post("/api/proxy").handler(this::handlePost);
router.get("/api/proxy").handler(this::handleGet);
int port = 8080;
vertx.createHttpServer()
.requestHandler(router)
.listen(port).onComplete(http -> {
if (http.succeeded()) {
System.out.println("Proxy server started on port " + port);
startPromise.complete();
} else {
startPromise.fail(http.cause());
}
});
}
private void handlePost(RoutingContext ctx) {
String apiServer = System.getenv("IIKO_API_SERVER");
String apiLogin = System.getenv("IIKO_API_LOGIN");
String apiPass = System.getenv("IIKO_API_PASS");
String externalEndpoint = System.getenv("IIKO_API_ENDPOINT");
if (externalEndpoint == null || externalEndpoint.isBlank()) {
externalEndpoint = "/your-endpoint";
}
if (apiServer == null || apiLogin == null || apiPass == null) {
fail(ctx, 500, "Missing required environment variables: IIKO_API_SERVER, IIKO_API_LOGIN, IIKO_API_PASS");
return;
}
JsonObject body = ctx.body().asJsonObject();
if (body == null) {
fail(ctx, 400, "Request body must be JSON");
return;
}
String signature = sha1(apiPass);
String authUrl = "https://" + apiServer + ":443/resto/api/auth?login=" + apiLogin + "&pass=" + signature;
String finalExternalEndpoint = externalEndpoint;
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 targetUrl = "https://" + apiServer + finalExternalEndpoint;
webClient.request(HttpMethod.POST, targetUrl)
.putHeader("Content-Type", "application/json")
.as(BodyCodec.jsonObject())
.sendJsonObject(body)
.onSuccess(apiResp -> {
webClient.getAbs("https://" + apiServer + ":443/resto/api/logout?key=" + token)
.send()
.onFailure(err -> System.err.println("Logout failed: " + err.getMessage()));
if (apiResp.statusCode() == 200) {
ctx.response().setStatusCode(200).end(apiResp.body().encode());
} else {
fail(ctx, apiResp.statusCode(), "External API error: " + apiResp.statusMessage());
}
})
.onFailure(err -> fail(ctx, 500, "Request to external API failed: " + err.getMessage()));
})
.onFailure(err -> fail(ctx, 500, "Auth request failed: " + err.getMessage()));
}
private void handleGet(RoutingContext ctx) {
String presetId = ctx.queryParam("presetId").stream().findFirst().orElse(null);
String dateFrom = ctx.queryParam("dateFrom").stream().findFirst().orElse(null);
String dateTo = ctx.queryParam("dateTo").stream().findFirst().orElse(null);
String server = ctx.queryParam("server").stream().findFirst().orElse(null);
String password = ctx.queryParam("password").stream().findFirst().orElse(null);
String login = ctx.queryParam("login").stream().findFirst().orElse(null);
String type = ctx.queryParam("type").stream().findFirst().orElse(null);
String rootType = ctx.queryParam("rootType").stream().findFirst().orElse(null);
if (server == null || login == null || password == null) {
fail(ctx, 400, "Missing required parameters: server, login, password");
return;
}
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;
if ("entity".equals(type)) {
dataUrl = "https://" + server + "/resto/api/v2/entities/list?key=" + token;
if (rootType != null && !rootType.isBlank()) {
dataUrl += "&rootType=" + rootType;
}
} else {
if (presetId == null || dateFrom == null || dateTo == null) {
fail(ctx, 400, "Missing presetId, dateFrom or dateTo for report request");
return;
}
dataUrl = "https://" + server + "/resto/api/v2/reports/olap/byPresetId/" + presetId +
"?key=" + token + "&dateFrom=" + dateFrom + "&dateTo=" + dateTo;
}
System.out.println("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 -> System.err.println("Logout failed: " + err.getMessage()));
if (dataResp.statusCode() == 200) {
JsonObject responseBody = dataResp.body();
if ("entity".equals(type)) {
ctx.response().setStatusCode(200).end(responseBody.encode());
} else {
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) {
System.err.println("Error: " + message);
ctx.response().setStatusCode(status).end(new JsonObject().put("error", message).encode());
}
}
// > GET /api/proxy?server=folk-amber-co.iiko.it&login=4444&password=4444&presetId=7ddc40c3-9d5f-408f-aa1e-652964b36c6c&dateFrom=2026-04-10&dateTo=2026-04-17 HTTP/1.1
// > Host: localhost:8080
// > access-token: ddb4ab653b9194ec1ea5448cee2a8a26282b0866c1d4a86e98e9b0f84bc91944
// > User-Agent: v2raytun/ios
// > X-App-Version: 2.4.3
// > X-Device-Model: iPhone 11 Pro
// > X-Device-OS: iOS
// > X-HWID: HHS8JDJN-F2EB-HFBS-KMWX-234FA7B95JSC
// > X-Ver-OS: 26.0
// > Accept: */*

View File

@@ -90,4 +90,45 @@ public class RestaurantService {
return array;
});
}
public Future<JsonObject> findById(int id) {
return SqlTemplate.forQuery(pool,
"SELECT id, name, login, password, host, created, updated FROM restaurants WHERE id = #{id}")
.mapTo(row -> new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("login", row.getString("login"))
.put("password", row.getString("password"))
.put("host", row.getString("host"))
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null))
.execute(Collections.singletonMap("id", id))
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
}
public Future<Void> updateRestaurant(int id, String name, String login, String password, String host) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("name", name);
params.put("login", login);
params.put("host", host);
String sql;
if (password != null && !password.isEmpty()) {
params.put("password", password);
sql = "UPDATE restaurants SET name = #{name}, login = #{login}, password = #{password}, host = #{host} WHERE id = #{id}";
} else {
sql = "UPDATE restaurants SET name = #{name}, login = #{login}, host = #{host} WHERE id = #{id}";
}
return SqlTemplate.forUpdate(pool, sql)
.execute(params)
.mapEmpty();
}
public Future<Void> deleteRestaurant(int id) {
return SqlTemplate.forUpdate(pool, "DELETE FROM restaurants WHERE id = #{id}")
.execute(Collections.singletonMap("id", id))
.mapEmpty();
}
}

View File

@@ -89,6 +89,32 @@ public class UserService {
});
}
public Future<Void> updateUser(int id, String login, String password, String ip) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("login", login);
params.put("ip", ip);
String sql;
if (password != null && !password.isEmpty()) {
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
params.put("password", hash);
sql = "UPDATE users SET login = #{login}, password = #{password}, ip = #{ip} WHERE id = #{id}";
} else {
sql = "UPDATE users SET login = #{login}, ip = #{ip} WHERE id = #{id}";
}
return SqlTemplate.forUpdate(pool, sql)
.execute(params)
.mapEmpty();
}
public Future<Void> deleteUser(int id) {
return SqlTemplate.forUpdate(pool, "DELETE FROM users WHERE id = #{id}")
.execute(Collections.singletonMap("id", id))
.mapEmpty();
}
public boolean checkPassword(String plain, String hash) {
try {
return BCrypt.checkpw(plain, hash);

View File

@@ -31,6 +31,7 @@ public class HealthCheckService {
long time = System.currentTimeMillis() - start;
if ("PONG".equalsIgnoreCase(response.toString())) {
JsonObject data = new JsonObject()
.put("name", "redis")
.put("latency_ms", time);
future.complete(Status.OK(data));
} else {
@@ -47,6 +48,7 @@ public class HealthCheckService {
.onSuccess(rs -> {
long time = System.currentTimeMillis() - start;
JsonObject data = new JsonObject()
.put("name", "database")
.put("latency_ms", time);
future.complete(Status.OK(data));
})