210 lines
6.9 KiB
Java
210 lines
6.9 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;
|
|
|
|
@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());
|
|
|
|
// Инициализация БД (создание таблицы users)
|
|
userService.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.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.route("/*").handler(StaticHandler.create("webroot")
|
|
// .setCachingEnabled(false)
|
|
// .setIndexPage("index.html"));
|
|
|
|
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()));
|
|
}
|
|
}
|