up
This commit is contained in:
@@ -4,6 +4,7 @@ import io.vertx.config.ConfigRetriever;
|
||||
import io.vertx.config.ConfigRetrieverOptions;
|
||||
import io.vertx.config.ConfigStoreOptions;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
@@ -14,6 +15,7 @@ import io.vertx.ext.web.handler.StaticHandler;
|
||||
import io.vertx.ext.web.sstore.LocalSessionStore;
|
||||
import io.vertx.ext.web.sstore.SessionStore;
|
||||
|
||||
import io.vertx.ext.web.sstore.redis.RedisSessionStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import su.xserver.iikocon.config.AppConfig;
|
||||
@@ -21,6 +23,10 @@ import su.xserver.iikocon.service.DataBaseService;
|
||||
import su.xserver.iikocon.service.HealthCheckService;
|
||||
import su.xserver.iikocon.service.RedisService;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MainVerticle extends AbstractVerticle {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger("[MainVerticle]");
|
||||
@@ -32,6 +38,7 @@ public class MainVerticle extends AbstractVerticle {
|
||||
|
||||
private UserService userService;
|
||||
private RestaurantService restaurantService;
|
||||
private SettingsService settingsService;
|
||||
|
||||
@Override
|
||||
public void start(Promise<Void> startPromise) {
|
||||
@@ -57,21 +64,21 @@ public class MainVerticle extends AbstractVerticle {
|
||||
// Инициализация сервисов
|
||||
userService = new UserService(db.getPool());
|
||||
restaurantService = new RestaurantService(db.getPool());
|
||||
settingsService = new SettingsService(db.getPool());
|
||||
|
||||
// Инициализация БД (создание таблицы users)
|
||||
userService.initDatabase()
|
||||
.onSuccess(v -> log.info("Database initialized successfully"))
|
||||
.onFailure(err -> {
|
||||
log.error("Failed to initialize database", err);
|
||||
startPromise.fail(err);
|
||||
});
|
||||
|
||||
restaurantService.initDatabase()
|
||||
.onSuccess(v -> log.info("Database initialized successfully"))
|
||||
.onFailure(err -> {
|
||||
log.error("Failed to initialize database", err);
|
||||
startPromise.fail(err);
|
||||
});
|
||||
userService.initDatabase().onFailure(err -> {
|
||||
log.error("Failed to initialize database", err);
|
||||
startPromise.fail(err);
|
||||
});
|
||||
restaurantService.initDatabase().onFailure(err -> {
|
||||
log.error("Failed to initialize database", err);
|
||||
startPromise.fail(err);
|
||||
});
|
||||
settingsService.initDatabase().onFailure(err -> {
|
||||
log.error("Failed to initialize database", err);
|
||||
startPromise.fail(err);
|
||||
});
|
||||
|
||||
Router router = initRouter();
|
||||
|
||||
@@ -90,7 +97,7 @@ public class MainVerticle extends AbstractVerticle {
|
||||
.setSessionCookieName("admin.session")
|
||||
.setCookieHttpOnlyFlag(true)
|
||||
.setCookieSecureFlag(false)
|
||||
.setSessionTimeout(3600000); // 1 час
|
||||
.setSessionTimeout(3600000);
|
||||
|
||||
// Роутер
|
||||
Router router = Router.router(vertx);
|
||||
@@ -144,6 +151,21 @@ public class MainVerticle extends AbstractVerticle {
|
||||
|
||||
router.post("/api/logout").handler(authHandler::handleLogout);
|
||||
|
||||
router.post("/api/register").handler(rc -> {
|
||||
JsonObject body = rc.body().asJsonObject();
|
||||
String login = body.getString("login");
|
||||
String email = body.getString("email");
|
||||
String password = body.getString("password");
|
||||
String ip = rc.request().remoteAddress().host();
|
||||
if (login == null || email == null || password == null) {
|
||||
rc.response().setStatusCode(400).end("Missing fields");
|
||||
return;
|
||||
}
|
||||
userService.createUser(login, email, password, ip)
|
||||
.onSuccess(v -> rc.response().setStatusCode(201).end(new JsonObject().put("success", true).encode()))
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
router.route("/api/admin/*").handler(authHandler::requireAuth);
|
||||
|
||||
router.get("/api/admin/users").handler(rc -> userService.getAllUsers().onComplete(ar -> {
|
||||
@@ -159,13 +181,14 @@ public class MainVerticle extends AbstractVerticle {
|
||||
router.post("/api/admin/users").handler(rc -> {
|
||||
JsonObject body = rc.body().asJsonObject();
|
||||
String login = body.getString("login");
|
||||
String email = body.getString("email");
|
||||
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");
|
||||
if (login == null || email == null || password == null) {
|
||||
rc.response().setStatusCode(400).end("Missing login, email or password");
|
||||
return;
|
||||
}
|
||||
userService.createUser(login, password, ip)
|
||||
userService.createUser(login, email, password, ip, true)
|
||||
.onSuccess(v -> rc.response().setStatusCode(201).end())
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
@@ -174,13 +197,14 @@ public class MainVerticle extends AbstractVerticle {
|
||||
int id = Integer.parseInt(rc.pathParam("id"));
|
||||
JsonObject body = rc.body().asJsonObject();
|
||||
String login = body.getString("login");
|
||||
String email = body.getString("email");
|
||||
String password = body.getString("password");
|
||||
String ip = rc.request().remoteAddress().host();
|
||||
if (login == null) {
|
||||
rc.response().setStatusCode(400).end("Missing login");
|
||||
if (login == null || email == null) {
|
||||
rc.response().setStatusCode(400).end("Missing login or email");
|
||||
return;
|
||||
}
|
||||
userService.updateUser(id, login, password, ip)
|
||||
userService.updateUser(id, login, email, password, ip)
|
||||
.onSuccess(v -> rc.response().end())
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
@@ -192,6 +216,14 @@ public class MainVerticle extends AbstractVerticle {
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
router.put("/api/admin/users/:id/activate").handler(rc -> {
|
||||
int id = Integer.parseInt(rc.pathParam("id"));
|
||||
boolean active = Boolean.parseBoolean(rc.queryParam("active").get(0));
|
||||
userService.setActive(id, active)
|
||||
.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");
|
||||
@@ -265,6 +297,29 @@ public class MainVerticle extends AbstractVerticle {
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
// Получение всех настроек
|
||||
router.get("/api/settings").handler(rc -> {
|
||||
settingsService.getAll()
|
||||
.onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode()))
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
// Обновление настроек (админ)
|
||||
router.put("/api/admin/settings").handler(rc -> {
|
||||
JsonObject body = rc.body().asJsonObject();
|
||||
List<Future<Void>> futures = new ArrayList<>(); // явно указываем тип Future<Void>
|
||||
body.forEach(entry -> futures.add(settingsService.set(entry.getKey(), entry.getValue().toString())));
|
||||
Future.all(futures)
|
||||
.onSuccess(v -> rc.response().end())
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
// Количество активных сессий (на основе Redis)
|
||||
router.get("/api/admin/active-sessions").handler(rc -> {
|
||||
// TODO: реализовать подсчёт активных сессий через Redis или другой механизм
|
||||
rc.response().end(new JsonObject().put("count", 0).encode());
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,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,
|
||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
67
src/main/java/su/xserver/iikocon/SettingsService.java
Normal file
67
src/main/java/su/xserver/iikocon/SettingsService.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package su.xserver.iikocon;
|
||||
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.sqlclient.Pool;
|
||||
import io.vertx.sqlclient.templates.SqlTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SettingsService {
|
||||
private final Pool pool;
|
||||
|
||||
public SettingsService(Pool pool) { this.pool = pool; }
|
||||
|
||||
public Future<Void> initDatabase() {
|
||||
String createTable = """
|
||||
CREATE TABLE IF NOT EXISTS app_settings (
|
||||
setting_key VARCHAR(255) PRIMARY KEY,
|
||||
setting_value TEXT
|
||||
)
|
||||
""";
|
||||
return pool.query(createTable).execute()
|
||||
.compose(v -> setIfAbsent("site_name", "Admin Panel"))
|
||||
.compose(v -> setIfAbsent("site_description", "Powerful administration dashboard"))
|
||||
.compose(v -> setIfAbsent("theme", "light"))
|
||||
.compose(v -> setIfAbsent("items_per_page", "20"))
|
||||
.compose(v -> setIfAbsent("enable_registration", "true"))
|
||||
.compose(v -> setIfAbsent("maintenance_mode", "false"))
|
||||
.compose(v -> setIfAbsent("default_language", "en"))
|
||||
.compose(v -> setIfAbsent("session_timeout_minutes", "60"))
|
||||
.compose(v -> setIfAbsent("logo_url", "/assets/logo.png"))
|
||||
.mapEmpty();
|
||||
}
|
||||
|
||||
private Future<Void> setIfAbsent(String key, String defaultValue) {
|
||||
return get(key).compose(existing -> {
|
||||
if (existing == null) {
|
||||
return set(key, defaultValue);
|
||||
}
|
||||
return Future.succeededFuture();
|
||||
});
|
||||
}
|
||||
|
||||
public Future<String> 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);
|
||||
}
|
||||
|
||||
public Future<Void> 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))
|
||||
.mapEmpty();
|
||||
}
|
||||
|
||||
public Future<JsonObject> getAll() {
|
||||
return pool.query("SELECT setting_key, setting_value FROM app_settings")
|
||||
.execute()
|
||||
.map(rows -> {
|
||||
JsonObject json = new JsonObject();
|
||||
rows.forEach(row -> json.put(row.getString("setting_key"), row.getString("setting_value")));
|
||||
return json;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,11 @@ public class SetupHandler {
|
||||
|
||||
String login = body.getString("login");
|
||||
String password = body.getString("password");
|
||||
String email = body.getString("email");
|
||||
|
||||
if (email == null || email.isBlank()) {
|
||||
email = login + "@admin.local"; // значение по умолчанию
|
||||
}
|
||||
|
||||
if (login == null || password == null || login.length() < 3 || password.length() < 6) {
|
||||
ctx.response().setStatusCode(400)
|
||||
@@ -47,7 +52,7 @@ public class SetupHandler {
|
||||
}
|
||||
|
||||
String ip = ctx.request().remoteAddress().host();
|
||||
userService.createUser(login, password, ip).onComplete(cr -> {
|
||||
userService.createUser(login, email, password, ip, true).onComplete(cr -> {
|
||||
if (cr.succeeded()) {
|
||||
ctx.response().setStatusCode(201)
|
||||
.end(new JsonObject().put("success", true).encode());
|
||||
|
||||
@@ -21,16 +21,17 @@ public class UserService {
|
||||
|
||||
public Future<Void> initDatabase() {
|
||||
String createTable = """
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
login VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
ip VARCHAR(45)
|
||||
)
|
||||
""";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
login VARCHAR(255) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
active BOOLEAN DEFAULT FALSE,
|
||||
ip VARCHAR(45),
|
||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
""";
|
||||
return pool.query(createTable).execute().mapEmpty();
|
||||
}
|
||||
|
||||
@@ -40,20 +41,38 @@ public class UserService {
|
||||
.map(rows -> rows.iterator().next().getLong("cnt"));
|
||||
}
|
||||
|
||||
public Future<Void> createUser(String login, String password, String ip) {
|
||||
public Future<Void> createUser(String login, String email, String password, String ip, boolean active) {
|
||||
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("login", login);
|
||||
params.put("password", hash);
|
||||
params.put("ip", ip);
|
||||
|
||||
Map<String, Object> params = Map.of(
|
||||
"login", login,
|
||||
"email", email,
|
||||
"password", hash,
|
||||
"ip", ip,
|
||||
"active", active
|
||||
);
|
||||
return SqlTemplate.forUpdate(pool,
|
||||
"INSERT INTO users (login, password, ip) VALUES (#{login}, #{password}, #{ip})")
|
||||
"INSERT INTO users (login, email, password, ip, active) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active})")
|
||||
.execute(params)
|
||||
.mapEmpty();
|
||||
}
|
||||
|
||||
// Существующий метод оставляем, но он будет создавать неактивного пользователя (active = false)
|
||||
public Future<Void> createUser(String login, String email, String password, String ip) {
|
||||
return createUser(login, email, password, ip, false);
|
||||
}
|
||||
|
||||
public Future<Void> 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();
|
||||
}
|
||||
|
||||
public Future<JsonObject> findByEmail(String email) {
|
||||
return SqlTemplate.forQuery(pool, "SELECT id, login, email, password, active, ip, created, updated FROM users WHERE email = #{email}")
|
||||
.mapTo(this::toJson)
|
||||
.execute(Map.of("email", email))
|
||||
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
|
||||
}
|
||||
|
||||
public Future<JsonObject> findByLogin(String login) {
|
||||
return SqlTemplate.forQuery(pool,
|
||||
"SELECT id, login, password, created, updated, ip FROM users WHERE login = #{login}")
|
||||
@@ -71,42 +90,30 @@ public class UserService {
|
||||
}
|
||||
|
||||
public Future<JsonArray> getAllUsers() {
|
||||
return pool.query("SELECT id, login, created, updated, ip FROM users ORDER BY id")
|
||||
return pool.query("SELECT id, login, email, active, ip, created, updated FROM users ORDER BY id")
|
||||
.execute()
|
||||
.map(rows -> {
|
||||
JsonArray array = new JsonArray();
|
||||
for (Row row : rows) {
|
||||
array.add(new JsonObject()
|
||||
.put("id", row.getInteger("id"))
|
||||
.put("login", row.getString("login"))
|
||||
.put("created", row.getLocalDateTime("created") != null ?
|
||||
row.getLocalDateTime("created").toString() : null)
|
||||
.put("updated", row.getLocalDateTime("updated") != null ?
|
||||
row.getLocalDateTime("updated").toString() : null)
|
||||
.put("ip", row.getString("ip")));
|
||||
}
|
||||
rows.forEach(row -> array.add(toJson(row)));
|
||||
return array;
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Void> updateUser(int id, String login, String password, String ip) {
|
||||
public Future<Void> updateUser(int id, String login, String email, String password, String ip) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("id", id);
|
||||
params.put("login", login);
|
||||
params.put("email", email);
|
||||
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}";
|
||||
sql = "UPDATE users SET login = #{login}, email = #{email}, password = #{password}, ip = #{ip} WHERE id = #{id}";
|
||||
} else {
|
||||
sql = "UPDATE users SET login = #{login}, ip = #{ip} WHERE id = #{id}";
|
||||
sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip} WHERE id = #{id}";
|
||||
}
|
||||
|
||||
return SqlTemplate.forUpdate(pool, sql)
|
||||
.execute(params)
|
||||
.mapEmpty();
|
||||
return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
|
||||
}
|
||||
|
||||
public Future<Void> deleteUser(int id) {
|
||||
@@ -122,4 +129,15 @@ public class UserService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonObject toJson(Row row) {
|
||||
return new JsonObject()
|
||||
.put("id", row.getInteger("id"))
|
||||
.put("login", row.getString("login"))
|
||||
.put("email", row.getString("email"))
|
||||
.put("active", row.getBoolean("active"))
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class HealthCheckService {
|
||||
long time = System.currentTimeMillis() - start;
|
||||
if ("PONG".equalsIgnoreCase(response.toString())) {
|
||||
JsonObject data = new JsonObject()
|
||||
.put("name", "redis")
|
||||
.put("name", "Redis")
|
||||
.put("latency_ms", time);
|
||||
future.complete(Status.OK(data));
|
||||
} else {
|
||||
@@ -42,7 +42,7 @@ public class HealthCheckService {
|
||||
});
|
||||
|
||||
// Database check
|
||||
healthCheckHandler.register("database", future -> {
|
||||
healthCheckHandler.register("DataBase", future -> {
|
||||
long start = System.currentTimeMillis();
|
||||
dbService.getPool().query("SELECT 1").execute()
|
||||
.onSuccess(rs -> {
|
||||
|
||||
Reference in New Issue
Block a user