From 1531215b43c46b5d6f88a1d282cb3fbd8762b3ca Mon Sep 17 00:00:00 2001 From: Danil-Bodry Date: Fri, 8 May 2026 13:55:46 +0300 Subject: [PATCH] fix: we no longer use sql-client-templates --- build.gradle.kts | 1 - libs/vertx-sql-client-templates-5.0.12.jar | Bin 30856 -> 0 bytes .../java/su/xserver/iikocon/MainVerticle.java | 4 +- .../iikocon/iiko/OlapQueryService.java | 87 ++++++-------- .../service/ExternalDataBaseService.java | 83 ++++++-------- .../iikocon/service/RestaurantService.java | 71 ++++++------ .../iikocon/service/SettingsService.java | 22 ++-- .../xserver/iikocon/service/UserService.java | 107 +++++++++--------- .../xserver/iikocon/test/DateRangeSetup.java | 8 -- 9 files changed, 175 insertions(+), 208 deletions(-) delete mode 100644 libs/vertx-sql-client-templates-5.0.12.jar diff --git a/build.gradle.kts b/build.gradle.kts index 39588ea..e79de0c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,7 +40,6 @@ dependencies { implementation("io.vertx:vertx-launcher-application") implementation("io.vertx:vertx-web-client") implementation("io.vertx:vertx-config") - implementation("io.vertx:vertx-sql-client-templates") implementation("io.vertx:vertx-health-check") implementation("io.vertx:vertx-web") implementation("io.vertx:vertx-mysql-client") diff --git a/libs/vertx-sql-client-templates-5.0.12.jar b/libs/vertx-sql-client-templates-5.0.12.jar deleted file mode 100644 index f95cea5d74942ecfb498a28174efce8c5bf7088d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30856 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

LdDzVszZTdHT*&$ABMTay$8W0NICKYyrA_rpW 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 diff --git a/src/main/java/su/xserver/iikocon/MainVerticle.java b/src/main/java/su/xserver/iikocon/MainVerticle.java index 93bab00..14388f5 100644 --- a/src/main/java/su/xserver/iikocon/MainVerticle.java +++ b/src/main/java/su/xserver/iikocon/MainVerticle.java @@ -569,7 +569,7 @@ public class MainVerticle extends AbstractVerticle { return; } - if (!olapQueryService.isValidTableName(tableName)) { + if (olapQueryService.isValidTableName(tableName)) { rc.response().setStatusCode(400).end("Invalid tableName: must start with a letter and contain only letters and digits"); return; } @@ -606,7 +606,7 @@ public class MainVerticle extends AbstractVerticle { return; } - if (!olapQueryService.isValidTableName(tableName)) { + if (olapQueryService.isValidTableName(tableName)) { rc.response().setStatusCode(400).end("Invalid tableName: must start with a letter and contain only letters and digits"); return; } diff --git a/src/main/java/su/xserver/iikocon/iiko/OlapQueryService.java b/src/main/java/su/xserver/iikocon/iiko/OlapQueryService.java index 9074b44..c3707c4 100644 --- a/src/main/java/su/xserver/iikocon/iiko/OlapQueryService.java +++ b/src/main/java/su/xserver/iikocon/iiko/OlapQueryService.java @@ -6,15 +6,14 @@ import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Tuple; -import io.vertx.sqlclient.templates.SqlTemplate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import su.xserver.iikocon.service.ExternalDataBaseService; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.*; public class OlapQueryService { - private static final Logger log = LoggerFactory.getLogger(OlapQueryService.class); private final Pool pool; private final ExternalDataBaseService externalDataBaseService; private final SqlGenerator sqlGenerator; @@ -62,17 +61,16 @@ public class OlapQueryService { public Future createQuery(String name, int dbConnectionId, JsonObject config, List restaurantIds, String generatedSql, boolean active) { JsonObject fullConfig = generateFullIikoJson(config); - Map params = Map.of( - "name", name, - "db_connection_id", dbConnectionId, - "config_json", config.encode(), - "full_config_json", fullConfig.encode(), - "sql_text", generatedSql != null ? generatedSql : "", - "active", active + String sql = "INSERT INTO olap_queries (name, db_connection_id, config_json, full_config_json, sql_text, active) VALUES (?, ?, ?, ?, ?, ?)"; + Tuple params = Tuple.of( + name, + dbConnectionId, + config.encode(), + fullConfig.encode(), + generatedSql != null ? generatedSql : "", + active ); - String sql = "INSERT INTO olap_queries (name, db_connection_id, config_json, full_config_json, sql_text, active) VALUES (#{name}, #{db_connection_id}, #{config_json}, #{full_config_json}, #{sql_text}, #{active})"; - return SqlTemplate.forUpdate(pool, sql) - .execute(params) + return pool.preparedQuery(sql).execute(params) .compose(rows -> getLastInsertId()) .compose(queryId -> linkRestaurants(queryId, restaurantIds).map(queryId)); } @@ -80,22 +78,23 @@ public class OlapQueryService { public Future updateQuery(int id, String name, int dbConnectionId, JsonObject config, List restaurantIds, String generatedSql, boolean active) { JsonObject fullConfig = generateFullIikoJson(config); - Map params = Map.of( - "id", id, - "name", name, - "db_connection_id", dbConnectionId, - "config_json", config.encode(), - "full_config_json", fullConfig.encode(), - "sql_text", generatedSql != null ? generatedSql : "", - "active", active + String sql = "UPDATE olap_queries SET name = ?, db_connection_id = ?, config_json = ?, full_config_json = ?, sql_text = ?, active = ? WHERE id = ?"; + Tuple params = Tuple.of( + name, + dbConnectionId, + config.encode(), + fullConfig.encode(), + generatedSql != null ? generatedSql : "", + active, + id ); - String sql = "UPDATE olap_queries SET name = #{name}, db_connection_id = #{db_connection_id}, config_json = #{config_json}, full_config_json = #{full_config_json}, sql_text = #{sql_text}, active = #{active} WHERE id = #{id}"; - return SqlTemplate.forUpdate(pool, sql) - .execute(params) + return pool.preparedQuery(sql).execute(params) .compose(v -> pool.query("DELETE FROM olap_query_restaurants WHERE query_id = " + id).execute() - .compose(del -> linkRestaurants(id, restaurantIds))).mapEmpty(); + .compose(del -> linkRestaurants(id, restaurantIds))) + .mapEmpty(); } + public JsonObject generateFullIikoJson(JsonObject clientConfig) { String reportType = clientConfig.getString("reportType", "SALES"); boolean buildSummary = clientConfig.getBoolean("buildSummary", false); @@ -153,17 +152,15 @@ public class OlapQueryService { } private JsonObject buildDateFilter(String reportType, String dateToStr, int daysBack) { - // Определяем корректную дату "до" (конец дня) - java.time.ZonedDateTime toDate; + ZonedDateTime toDate; if (dateToStr != null && !dateToStr.isEmpty()) { - toDate = java.time.LocalDate.parse(dateToStr).atStartOfDay(java.time.ZoneOffset.UTC); + toDate = LocalDate.parse(dateToStr).atStartOfDay(ZoneOffset.UTC); } else { - toDate = java.time.ZonedDateTime.now(java.time.ZoneOffset.UTC); + toDate = ZonedDateTime.now(ZoneOffset.UTC); } toDate = toDate.withHour(23).withMinute(59).withSecond(59).withNano(999_999_999); - java.time.ZonedDateTime fromDate = toDate.minusDays(Math.max(1, daysBack)) + ZonedDateTime fromDate = toDate.minusDays(Math.max(1, daysBack)) .withHour(0).withMinute(0).withSecond(0).withNano(0); - String filterKey = "TRANSACTIONS".equals(reportType) ? "DateTime.DateTyped" : "OpenDate.Typed"; return new JsonObject().put(filterKey, new JsonObject() .put("filterType", "DateRange") @@ -179,23 +176,19 @@ public class OlapQueryService { private Future linkRestaurants(int queryId, List restaurantIds) { if (restaurantIds == null || restaurantIds.isEmpty()) return Future.succeededFuture(); + String sql = "INSERT INTO olap_query_restaurants (query_id, restaurant_id) VALUES (?, ?)"; List> futures = new ArrayList<>(); for (Integer restId : restaurantIds) { - Map params = Map.of("query_id", queryId, "restaurant_id", restId); - futures.add(SqlTemplate.forUpdate(pool, - "INSERT INTO olap_query_restaurants (query_id, restaurant_id) VALUES (#{query_id}, #{restaurant_id})") - .execute(params).mapEmpty()); + futures.add(pool.preparedQuery(sql).execute(Tuple.of(queryId, restId)).mapEmpty()); } return Future.all(futures).mapEmpty(); } - // Удаление public Future deleteQuery(int id) { - return SqlTemplate.forUpdate(pool, "DELETE FROM olap_queries WHERE id = #{id}") - .execute(Map.of("id", id)).mapEmpty(); + String sql = "DELETE FROM olap_queries WHERE id = ?"; + return pool.preparedQuery(sql).execute(Tuple.of(id)).mapEmpty(); } - // Получить все запросы (без config_json, для списка) public Future getAllQueries() { String sql = """ SELECT q.id, q.name, q.db_connection_id, q.active, q.last_run, q.last_run_success, q.created, q.updated, @@ -226,13 +219,10 @@ public class OlapQueryService { }); } - - // Получить один запрос с полной конфигурацией public Future getQueryById(int id) { String querySql = "SELECT id, name, db_connection_id, config_json, sql_text, active, created, updated FROM olap_queries WHERE id = ?"; String restaurantsSql = "SELECT restaurant_id FROM olap_query_restaurants WHERE query_id = ?"; - - return pool.preparedQuery(querySql).execute(io.vertx.sqlclient.Tuple.of(id)) + return pool.preparedQuery(querySql).execute(Tuple.of(id)) .compose(rows -> { if (rows.size() == 0) return Future.succeededFuture(null); Row row = rows.iterator().next(); @@ -245,8 +235,7 @@ public class OlapQueryService { .put("active", row.getBoolean("active")) .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null) .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null); - - return pool.preparedQuery(restaurantsSql).execute(io.vertx.sqlclient.Tuple.of(id)) + return pool.preparedQuery(restaurantsSql).execute(Tuple.of(id)) .map(restRows -> { JsonArray restIds = new JsonArray(); restRows.forEach(restRow -> restIds.add(restRow.getInteger("restaurant_id"))); @@ -262,7 +251,6 @@ public class OlapQueryService { return pool.preparedQuery(sql).execute(Tuple.of(success, queryId)).mapEmpty(); } - // Генерация SQL на основе конфигурации и ID подключения public Future generateSql(JsonObject config, int dbConnectionId) { return externalDataBaseService.findById(dbConnectionId) .compose(conn -> { @@ -278,9 +266,8 @@ public class OlapQueryService { } public boolean isValidTableName(String tableName) { - if (tableName == null) return false; + if (tableName == null) return true; String trimmed = tableName.trim(); - // Первый символ — английская буква, далее буквы или цифры - return trimmed.matches("^[A-Za-z][A-Za-z0-9]*$"); + return !trimmed.matches("^[A-Za-z][A-Za-z0-9]*$"); } } diff --git a/src/main/java/su/xserver/iikocon/service/ExternalDataBaseService.java b/src/main/java/su/xserver/iikocon/service/ExternalDataBaseService.java index b78a9f5..7d5d378 100644 --- a/src/main/java/su/xserver/iikocon/service/ExternalDataBaseService.java +++ b/src/main/java/su/xserver/iikocon/service/ExternalDataBaseService.java @@ -11,13 +11,9 @@ import io.vertx.jdbcclient.JDBCPool; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.Row; -import io.vertx.sqlclient.templates.SqlTemplate; +import io.vertx.sqlclient.Tuple; import su.xserver.iikocon.handler.AdminHandler; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - public class ExternalDataBaseService { private final Pool pool; private final Vertx vertx; @@ -106,7 +102,7 @@ public class ExternalDataBaseService { CREATE TABLE IF NOT EXISTS external_database ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) UNIQUE NOT NULL, - type VARCHAR(40) UNIQUE NOT NULL, + type VARCHAR(40) NOT NULL, host VARCHAR(255) NOT NULL, port INT NOT NULL, database VARCHAR(255) NOT NULL, @@ -120,18 +116,9 @@ public class ExternalDataBaseService { } public Future createDataBase(String name, String type, String host, int port, String database, String user, String password) { - Map params = Map.of( - "name", name, - "type", type, - "host", host, - "port", port, - "database", database, - "user", user, - "password", password - ); - return SqlTemplate.forUpdate(pool, - "INSERT INTO external_database (name, type, host, port, database, user, password) VALUES (#{name}, #{type}, #{host}, #{port}, #{database}, #{user}, #{password})") - .execute(params) + String sql = "INSERT INTO external_database (name, type, host, port, database, user, password) VALUES (?, ?, ?, ?, ?, ?, ?)"; + return pool.preparedQuery(sql) + .execute(Tuple.of(name, type, host, port, database, user, password)) .mapEmpty(); } @@ -159,45 +146,47 @@ public class ExternalDataBaseService { } public Future findById(int id) { - return SqlTemplate.forQuery(pool, - "SELECT id, name, type, host, port, database, user, password, created, updated FROM external_database WHERE id = #{id}") - .mapTo(row -> new JsonObject() - .put("id", row.getInteger("id")) - .put("name", row.getString("name")) - .put("type", row.getString("type")) - .put("host", row.getString("host")) - .put("port", row.getInteger("port")) - .put("database", row.getString("database")) - .put("user", row.getString("user")) - .put("password", row.getString("password")) - .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); + String sql = "SELECT id, name, type, host, port, database, user, password, created, updated FROM external_database WHERE id = ?"; + return pool.preparedQuery(sql) + .execute(Tuple.of(id)) + .map(rows -> { + if (rows.iterator().hasNext()) { + Row row = rows.iterator().next(); + return new JsonObject() + .put("id", row.getInteger("id")) + .put("name", row.getString("name")) + .put("type", row.getString("type")) + .put("host", row.getString("host")) + .put("port", row.getInteger("port")) + .put("database", row.getString("database")) + .put("user", row.getString("user")) + .put("password", row.getString("password")) + .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null) + .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null); + } else { + return null; + } + }); } public Future updateDataBase(int id, String name, String type, String host, int port, String database, String user, String password) { - Map params = new HashMap<>(); - params.put("id", id); - params.put("name", name); - params.put("type", type); - params.put("host", host); - params.put("port", port); - params.put("database", database); - params.put("user", user); String sql; + Tuple params; if (password != null && !password.isEmpty()) { - params.put("password", password); - sql = "UPDATE external_database SET name = #{name}, type = #{type}, host = #{host}, port = #{port}, database = #{database}, user = #{user}, password = #{password} WHERE id = #{id}"; + sql = "UPDATE external_database SET name = ?, type = ?, host = ?, port = ?, database = ?, user = ?, password = ? WHERE id = ?"; + params = Tuple.of(name, type, host, port, database, user, password, id); } else { - sql = "UPDATE external_database SET name = #{name}, type = #{type}, host = #{host}, port = #{port}, database = #{database}, user = #{user} WHERE id = #{id}"; + sql = "UPDATE external_database SET name = ?, type = ?, host = ?, port = ?, database = ?, user = ? WHERE id = ?"; + params = Tuple.of(name, type, host, port, database, user, id); } - return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty(); + return pool.preparedQuery(sql) + .execute(params) + .mapEmpty(); } public Future deleteDataBase(int id) { - return SqlTemplate.forUpdate(pool, "DELETE FROM external_database WHERE id = #{id}") - .execute(Collections.singletonMap("id", id)) + return pool.preparedQuery("DELETE FROM external_database WHERE id = ?") + .execute(Tuple.of(id)) .mapEmpty(); } diff --git a/src/main/java/su/xserver/iikocon/service/RestaurantService.java b/src/main/java/su/xserver/iikocon/service/RestaurantService.java index 990b377..3ee04d5 100644 --- a/src/main/java/su/xserver/iikocon/service/RestaurantService.java +++ b/src/main/java/su/xserver/iikocon/service/RestaurantService.java @@ -5,14 +5,11 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.Row; -import io.vertx.sqlclient.templates.SqlTemplate; +import io.vertx.sqlclient.Tuple; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; public class RestaurantService { private final Pool pool; @@ -43,7 +40,7 @@ public class RestaurantService { CREATE TABLE IF NOT EXISTS restaurants ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) UNIQUE NOT NULL, - login VARCHAR(255) UNIQUE NOT NULL, + login VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, host VARCHAR(255) NOT NULL, https BOOLEAN DEFAULT FALSE, @@ -56,15 +53,9 @@ public class RestaurantService { public Future createRestaurant(String name, String login, String password, String host, boolean https) { String hashedPassword = hashPassword(password); - Map params = Map.of( - "name", name, - "login", login, - "password", hashedPassword, - "host", host, - "https", https - ); - return SqlTemplate.forUpdate(pool, - "INSERT INTO restaurants (name, login, password, host, https) VALUES (#{name}, #{login}, #{password}, #{host}, #{https})") + String sql = "INSERT INTO restaurants (name, login, password, host, https) VALUES (?, ?, ?, ?, ?)"; + Tuple params = Tuple.of(name, login, hashedPassword, host, https); + return pool.preparedQuery(sql) .execute(params) .mapEmpty(); } @@ -91,42 +82,44 @@ public class RestaurantService { } public Future findById(int id) { - return SqlTemplate.forQuery(pool, - "SELECT id, name, login, password, https, 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("https", row.getBoolean("https")) - .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); + String sql = "SELECT id, name, login, password, https, host, created, updated FROM restaurants WHERE id = ?"; + return pool.preparedQuery(sql) + .execute(Tuple.of(id)) + .map(rows -> { + Row row = rows.iterator().hasNext() ? rows.iterator().next() : null; + if (row == null) return null; + return new JsonObject() + .put("id", row.getInteger("id")) + .put("name", row.getString("name")) + .put("login", row.getString("login")) + .put("password", row.getString("password")) + .put("https", row.getBoolean("https")) + .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); + }); } public Future updateRestaurant(int id, String name, String login, String password, String host, boolean https) { - Map params = new HashMap<>(); - params.put("id", id); - params.put("name", name); - params.put("login", login); - params.put("host", host); - params.put("https", https); String sql; + Tuple params; if (password != null && !password.isEmpty()) { String hashedPassword = hashPassword(password); - params.put("password", hashedPassword); - sql = "UPDATE restaurants SET name = #{name}, login = #{login}, password = #{password}, host = #{host}, https = #{https} WHERE id = #{id}"; + sql = "UPDATE restaurants SET name = ?, login = ?, password = ?, host = ?, https = ? WHERE id = ?"; + params = Tuple.of(name, login, hashedPassword, host, https, id); } else { - sql = "UPDATE restaurants SET name = #{name}, login = #{login}, host = #{host}, https = #{https} WHERE id = #{id}"; + sql = "UPDATE restaurants SET name = ?, login = ?, host = ?, https = ? WHERE id = ?"; + params = Tuple.of(name, login, host, https, id); } - return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty(); + return pool.preparedQuery(sql) + .execute(params) + .mapEmpty(); } public Future deleteRestaurant(int id) { - return SqlTemplate.forUpdate(pool, "DELETE FROM restaurants WHERE id = #{id}") - .execute(Collections.singletonMap("id", id)) + String sql = "DELETE FROM restaurants WHERE id = ?"; + return pool.preparedQuery(sql) + .execute(Tuple.of(id)) .mapEmpty(); } } diff --git a/src/main/java/su/xserver/iikocon/service/SettingsService.java b/src/main/java/su/xserver/iikocon/service/SettingsService.java index acd56b1..939eb67 100644 --- a/src/main/java/su/xserver/iikocon/service/SettingsService.java +++ b/src/main/java/su/xserver/iikocon/service/SettingsService.java @@ -4,10 +4,10 @@ import io.vertx.core.Future; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Pool; -import io.vertx.sqlclient.templates.SqlTemplate; +import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.Tuple; import java.util.List; -import java.util.Map; public class SettingsService { private final Pool pool; @@ -136,16 +136,20 @@ public class SettingsService { } public Future get(String key) { - return SqlTemplate.forQuery(pool, "SELECT setting_value FROM app_settings WHERE setting_key = #{key}") - .execute(Map.of("key", key)) - .map(rows -> rows.iterator().hasNext() ? rows.iterator().next().getString("setting_value") : null); + String sql = "SELECT setting_value FROM app_settings WHERE setting_key = ?"; + return pool.preparedQuery(sql) + .execute(Tuple.of(key)) + .map(rows -> { + Row row = rows.iterator().hasNext() ? rows.iterator().next() : null; + return row != null ? row.getString("setting_value") : null; + }); } public Future set(String key, String value) { - return SqlTemplate.forUpdate(pool, - "INSERT INTO app_settings (setting_key, setting_value) VALUES (#{key}, #{value}) " + - "ON DUPLICATE KEY UPDATE setting_value = #{value}") - .execute(Map.of("key", key, "value", value)) + String sql = "INSERT INTO app_settings (setting_key, setting_value) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE setting_value = ?"; + return pool.preparedQuery(sql) + .execute(Tuple.of(key, value, value)) .mapEmpty(); } diff --git a/src/main/java/su/xserver/iikocon/service/UserService.java b/src/main/java/su/xserver/iikocon/service/UserService.java index 18669e9..4e5e336 100644 --- a/src/main/java/su/xserver/iikocon/service/UserService.java +++ b/src/main/java/su/xserver/iikocon/service/UserService.java @@ -6,7 +6,6 @@ import io.vertx.core.json.JsonObject; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Tuple; -import io.vertx.sqlclient.templates.SqlTemplate; import org.mindrot.jbcrypt.BCrypt; import java.util.*; @@ -44,18 +43,9 @@ public class UserService { public Future createUser(String login, String email, String password, String ip, boolean active, String role) { String hash = BCrypt.hashpw(password, BCrypt.gensalt()); - Map params = Map.of( - "login", login, - "email", email, - "password", hash, - "ip", ip, - "active", active, - "role", role - ); - return SqlTemplate.forUpdate(pool, - "INSERT INTO users (login, email, password, ip, active, role) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active}, #{role})") - .execute(params) - .mapEmpty(); + String sql = "INSERT INTO users (login, email, password, ip, active, role) VALUES (?, ?, ?, ?, ?, ?)"; + Tuple params = Tuple.of(login, email, hash, ip, active, role); + return pool.preparedQuery(sql).execute(params).mapEmpty(); } public Future createUser(String login, String email, String password, String ip, boolean active) { @@ -67,8 +57,9 @@ public class UserService { } public Future setActive(int id, boolean active) { - return SqlTemplate.forUpdate(pool, "UPDATE users SET active = #{active} WHERE id = #{id}") - .execute(Map.of("id", id, "active", active)).mapEmpty(); + return pool.preparedQuery("UPDATE users SET active = ? WHERE id = ?") + .execute(Tuple.of(active, id)) + .mapEmpty(); } public Future findByLoginOrEmail(String loginOrEmail) { @@ -106,24 +97,30 @@ public class UserService { } public Future updateUser(int id, String login, String email, String password, String ip, String role) { - Map params = new HashMap<>(); - params.put("id", id); - params.put("login", login); - params.put("email", email); - params.put("ip", ip); - if (role != null) params.put("role", role); + List values = new ArrayList<>(); + values.add(login); + values.add(email); + values.add(ip); + if (role != null) { + values.add(role); + } String sql; if (password != null && !password.isEmpty()) { String hash = BCrypt.hashpw(password, BCrypt.gensalt()); - params.put("password", hash); - sql = "UPDATE users SET login = #{login}, email = #{email}, password = #{password}, ip = #{ip}" - + (role != null ? ", role = #{role}" : "") + " WHERE id = #{id}"; + sql = "UPDATE users SET login = ?, email = ?, password = ?, ip = ?" + + (role != null ? ", role = ?" : "") + " WHERE id = ?"; + values.add(2, hash); } else { - sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip}" - + (role != null ? ", role = #{role}" : "") + " WHERE id = #{id}"; + sql = "UPDATE users SET login = ?, email = ?, ip = ?" + + (role != null ? ", role = ?" : "") + " WHERE id = ?"; } - return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty(); + + values.add(id); // для WHERE + + return pool.preparedQuery(sql) + .execute(Tuple.tuple(values)) + .mapEmpty(); } public Future updateUserIp(int userId, String ip) { @@ -133,54 +130,60 @@ public class UserService { } public Future deleteUser(int id) { - return SqlTemplate.forUpdate(pool, "DELETE FROM users WHERE id = #{id}") - .execute(Collections.singletonMap("id", id)) + return pool.preparedQuery("DELETE FROM users WHERE id = ?") + .execute(Tuple.of(id)) .mapEmpty(); } public Future getProfile(int userId) { - return SqlTemplate.forQuery(pool, - "SELECT id, login, email, role, language, ip, created, updated FROM users WHERE id = #{id}") - .mapTo(row -> new JsonObject() - .put("id", row.getInteger("id")) - .put("login", row.getString("login")) - .put("email", row.getString("email")) - .put("role", row.getString("role")) - .put("language", row.getString("language")) - .put("ip", row.getString("ip")) - .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null) - .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null)) - .execute(Map.of("id", userId)) - .map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null); + String sql = "SELECT id, login, email, role, language, ip, created, updated FROM users WHERE id = ?"; + return pool.preparedQuery(sql) + .execute(Tuple.of(userId)) + .map(rows -> { + if (rows.size() == 0) return null; + Row row = rows.iterator().next(); + return new JsonObject() + .put("id", row.getInteger("id")) + .put("login", row.getString("login")) + .put("email", row.getString("email")) + .put("role", row.getString("role")) + .put("language", row.getString("language")) + .put("ip", row.getString("ip")) + .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null) + .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null); + }); } public Future updateProfile(int userId, String email, String password, String language) { - Map params = new HashMap<>(); - params.put("id", userId); List setClauses = new ArrayList<>(); + List values = new ArrayList<>(); if (email != null) { - setClauses.add("email = #{email}"); - params.put("email", email); + setClauses.add("email = ?"); + values.add(email); } if (password != null && !password.isEmpty()) { String hash = BCrypt.hashpw(password, BCrypt.gensalt()); - setClauses.add("password = #{password}"); - params.put("password", hash); + setClauses.add("password = ?"); + values.add(hash); } if (language != null) { - setClauses.add("language = #{language}"); - params.put("language", language); + setClauses.add("language = ?"); + values.add(language); } if (setClauses.isEmpty()) { return Future.succeededFuture(); } - String sql = "UPDATE users SET " + String.join(", ", setClauses) + " WHERE id = #{id}"; - return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty(); + values.add(userId); + String sql = "UPDATE users SET " + String.join(", ", setClauses) + " WHERE id = ?"; + + return pool.preparedQuery(sql) + .execute(Tuple.tuple(values)) + .mapEmpty(); } public boolean checkPassword(String plain, String hash) { diff --git a/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java b/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java index 287233b..2e8a4ad 100644 --- a/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java +++ b/src/main/java/su/xserver/iikocon/test/DateRangeSetup.java @@ -18,13 +18,5 @@ public class DateRangeSetup { System.out.println("dateFrom=" + formattedDateFrom); System.out.println("dateTo=" + formattedDateTo); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - System.out.println("CodeGenerator=" + CodeGenerator.generateCorporateCode()); - } }