Files
iiko-connector/src/main/java/su/xserver/iikocon/MainVerticle.java
2026-04-18 13:32:57 +03:00

386 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package su.xserver.iikocon;
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;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.sstore.LocalSessionStore;
import io.vertx.ext.web.sstore.SessionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import su.xserver.iikocon.config.AppConfig;
import su.xserver.iikocon.service.DataBaseService;
import su.xserver.iikocon.service.HealthCheckService;
import su.xserver.iikocon.service.RedisService;
import java.util.ArrayList;
import java.util.List;
public class MainVerticle extends AbstractVerticle {
private final Logger log = LoggerFactory.getLogger("[MainVerticle]");
private DataBaseService db;
private RedisService redis;
private HttpServer httpServer;
private AppConfig config;
private UserService userService;
private RestaurantService restaurantService;
private SettingsService settingsService;
@Override
public void start(Promise<Void> startPromise) {
ConfigStoreOptions classpathStore = new ConfigStoreOptions()
.setType("file")
.setFormat("json")
.setConfig(new JsonObject().put("path", "config.json").put("hierarchical", true))
.setOptional(false);
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
.addStore(classpathStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig()
.onSuccess(cfg -> {
config = AppConfig.from(cfg);
db = new DataBaseService(vertx, config.database);
redis = new RedisService(vertx, config.redis);
// Инициализация сервисов
userService = new UserService(db.getPool());
restaurantService = new RestaurantService(db.getPool());
settingsService = new SettingsService(db.getPool());
// Инициализация БД (создание таблицы users)
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();
startHttp(router, startPromise);
})
.onFailure(startPromise::fail);
}
private Router initRouter() {
// Настройка сессий (используем LocalSessionStore для простоты)
SessionStore sessionStore = LocalSessionStore.create(vertx);
SessionHandler sessionHandler = SessionHandler.create(sessionStore)
.setSessionCookieName("admin.session")
.setCookieHttpOnlyFlag(true)
.setCookieSecureFlag(false)
.setSessionTimeout(3600000);
// Роутер
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.route().handler(sessionHandler);
// CORS для разработки
router.route().handler(ctx -> {
ctx.response()
.putHeader("Access-Control-Allow-Origin", "*")
.putHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
.putHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
.putHeader("Access-Control-Allow-Credentials", "true");
if (ctx.request().method().name().equals("OPTIONS")) {
ctx.response().end();
} else {
ctx.next();
}
});
// ------ Раздаём Vue статику ------
router.route("/assets/*").handler(StaticHandler.create("webroot/assets"));
router.route("/favicon.ico").handler(ctx -> ctx.response().sendFile("webroot/favicon.ico"));
// ------ SPA fallback: отдаём index.html на все не-API запросы ------
router.route().handler(ctx -> {
if (ctx.request().path().startsWith("/api")) {
ctx.next();
} else {
ctx.response()
.putHeader("Content-Type", "text/html")
.sendFile("webroot/index.html");
}
});
// Health Checks
HealthCheckService healthCheckService = new HealthCheckService(vertx, redis, db);
healthCheckService.registerHealthCheck(router);
// API маршруты
AuthHandler authHandler = new AuthHandler(userService);
SetupHandler setupHandler = new SetupHandler(userService);
router.get("/api/status").handler(setupHandler::checkStatus);
router.post("/api/setup").handler(setupHandler::handleSetup);
router.post("/api/login").handler(authHandler::handleLogin);
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 -> {
if (ar.succeeded()) {
rc.response()
.putHeader("Content-Type", "application/json")
.end(ar.result().encode());
} else {
rc.response().setStatusCode(500).end(ar.cause().getMessage());
}
}));
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 || email == null || password == null) {
rc.response().setStatusCode(400).end("Missing login, email or password");
return;
}
// Создаём активного пользователя (active = true)
userService.createUser(login, email, password, ip, true)
.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 email = body.getString("email");
String password = body.getString("password");
String ip = rc.request().remoteAddress().host();
if (login == null || email == null) {
rc.response().setStatusCode(400).end("Missing login or email");
return;
}
userService.updateUser(id, login, email, 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"));
Integer currentUserId = rc.session().get("userId");
if (currentUserId != null && currentUserId == id) {
rc.response().setStatusCode(403).end(new JsonObject()
.put("error", "You cannot delete your own account")
.encode());
return;
}
userService.deleteUser(id)
.onSuccess(v -> rc.response().end())
.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));
Integer currentUserId = rc.session().get("userId");
if (currentUserId != null && currentUserId == id) {
rc.response().setStatusCode(403).end(new JsonObject().put("error", "You cannot deactivate yourself").encode());
return;
}
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");
if (userId != null) {
rc.response()
.putHeader("Content-Type", "application/json")
.end(new JsonObject()
.put("id", userId)
.put("login", rc.session().get("login"))
.encode());
} else {
rc.response().setStatusCode(401).end();
}
});
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()));
});
// Получение всех настроек
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.get("/api/settings/meta").handler(rc -> {
settingsService.getMetadata()
.onSuccess(meta -> rc.response().putHeader("Content-Type", "application/json").end(meta.encode()))
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
// Получить все настройки со значениями по умолчанию
router.get("/api/settings/all").handler(rc -> {
settingsService.getAllWithDefaults()
.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;
}
private void startHttp(Router router, Promise<Void> startPromise) {
// Запуск HTTP-сервера
httpServer = vertx.createHttpServer();
httpServer.requestHandler(router).listen(config.server.port, config.server.host)
.onSuccess(server -> {
log.info("HTTP server started on port {}", server.actualPort());
startPromise.complete();
})
.onFailure(throwable -> {
log.error(throwable.getMessage());
startPromise.fail(throwable);
});
}
@Override
public void stop(Promise<Void> stopPromise) {
this.httpServer.close()
.onSuccess(server -> {
log.info("Stop HTTP server");
this.db.disconnect();
this.redis.disconnect();
this.vertx.close()
.onSuccess(vertx -> {
log.info("Stop Vert.x");
stopPromise.complete();
})
.onFailure(throwable -> log.info(throwable.getMessage()));
})
.onFailure(throwable -> log.info(throwable.getMessage()));
}
}