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.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; 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; @Override public void start(Promise 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()); // Инициализация БД (создание таблицы 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); }); 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); // 1 час // Роутер 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.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 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"); 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())); }); return router; } private void startHttp(Router router, Promise 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 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())); } }