Files
iiko-connector/src/main/java/su/xserver/iikocon/MainVerticle.java
2026-04-18 11:33:21 +03:00

302 lines
11 KiB
Java

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<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());
// Инициализация БД (создание таблицы 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<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()));
}
}