688 lines
27 KiB
Java
688 lines
27 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.Future;
|
|
import io.vertx.core.Promise;
|
|
import io.vertx.core.buffer.Buffer;
|
|
import io.vertx.core.http.HttpServer;
|
|
import io.vertx.core.json.JsonArray;
|
|
import io.vertx.core.json.JsonObject;
|
|
import io.vertx.ext.web.Router;
|
|
import io.vertx.ext.web.RoutingContext;
|
|
import io.vertx.ext.web.client.HttpRequest;
|
|
import io.vertx.ext.web.client.HttpResponse;
|
|
import io.vertx.ext.web.client.WebClient;
|
|
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.SessionStore;
|
|
|
|
import io.vertx.ext.web.sstore.redis.RedisSessionStore;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import su.xserver.iikocon.config.AppConfig;
|
|
import su.xserver.iikocon.handler.*;
|
|
import su.xserver.iikocon.iiko.IikoHandler;
|
|
import su.xserver.iikocon.iiko.IikoOlapClient;
|
|
import su.xserver.iikocon.iiko.OlapQueryService;
|
|
import su.xserver.iikocon.service.*;
|
|
|
|
import java.net.URI;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
|
|
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 SessionStore sessionStore;
|
|
|
|
private UserService userService;
|
|
private RestaurantService restaurantService;
|
|
private ExternalDataBaseService externalDataBaseService;
|
|
private SettingsService settingsService;
|
|
private OlapQueryService olapQueryService;
|
|
|
|
@Override
|
|
public void start(Promise<Void> startPromise) throws ClassNotFoundException {
|
|
|
|
Class.forName("com.mysql.cj.jdbc.Driver");
|
|
Class.forName("org.postgresql.Driver");
|
|
|
|
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());
|
|
externalDataBaseService = new ExternalDataBaseService(db.getPool(), vertx);
|
|
olapQueryService = new OlapQueryService(db.getPool(), externalDataBaseService);
|
|
|
|
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);
|
|
});
|
|
externalDataBaseService.initDatabase().onFailure(err -> {
|
|
log.error("Failed to initialize database", err);
|
|
startPromise.fail(err);
|
|
});
|
|
olapQueryService.initDatabase().onFailure(err -> {
|
|
log.error("Failed to initialize database", err);
|
|
startPromise.fail(err);
|
|
});
|
|
|
|
createRouterAndStartHttp(startPromise);
|
|
|
|
})
|
|
.onFailure(startPromise::fail);
|
|
|
|
}
|
|
|
|
private void createRouterAndStartHttp(Promise<Void> startPromise) {
|
|
settingsService.get("session_timeout_minutes")
|
|
.compose(timeoutStr -> {
|
|
long timeoutMinutes = 60; // default
|
|
if (timeoutStr != null && !timeoutStr.isEmpty()) {
|
|
try {
|
|
timeoutMinutes = Long.parseLong(timeoutStr);
|
|
} catch (NumberFormatException ignored) {}
|
|
}
|
|
long timeoutMs = timeoutMinutes * 60 * 1000;
|
|
|
|
sessionStore = RedisSessionStore.create(vertx, redis.getRedis());
|
|
SessionHandler sessionHandler = SessionHandler.create(sessionStore)
|
|
.setSessionCookieName("admin.session")
|
|
.setCookieHttpOnlyFlag(true)
|
|
.setCookieSecureFlag(false)
|
|
.setSessionTimeout(timeoutMs);
|
|
|
|
Router router = initRouter(sessionHandler);
|
|
startHttp(router, startPromise);
|
|
return Future.succeededFuture();
|
|
})
|
|
.onFailure(err -> {
|
|
log.error("Failed to get session timeout", err);
|
|
startPromise.fail(err);
|
|
});
|
|
}
|
|
|
|
private void setupPhpmyadminProxy(Router router) {
|
|
if (config.pma == null || !config.pma.enabled) return;
|
|
|
|
String upstream = config.pma.upstream;
|
|
String basePath = config.pma.basePath;
|
|
|
|
final URI upstreamUri = URI.create(upstream);
|
|
final String host = upstreamUri.getHost();
|
|
int portTmp = upstreamUri.getPort();
|
|
if (portTmp == -1) {
|
|
portTmp = "https".equals(upstreamUri.getScheme()) ? 443 : 80;
|
|
}
|
|
final int port = portTmp;
|
|
|
|
final WebClient webClient = WebClient.create(vertx);
|
|
|
|
router.route(basePath + "/*").handler(ctx -> {
|
|
if (ctx.session() != null && "admin".equals(ctx.session().get("role"))) {
|
|
ctx.next();
|
|
} else {
|
|
ctx.response().putHeader("Location", "/").setStatusCode(302).end();
|
|
}
|
|
});
|
|
|
|
router.route(basePath + "/*").handler(ctx -> {
|
|
String targetPathBase = ctx.request().path().substring(basePath.length());
|
|
if (targetPathBase.isEmpty()) targetPathBase = "/";
|
|
String targetPath = targetPathBase;
|
|
String query = ctx.request().query();
|
|
if (query != null && !query.isEmpty()) {
|
|
targetPath += "?" + query;
|
|
}
|
|
final String targetPathFinal = targetPath;
|
|
|
|
final HttpRequest<Buffer> proxyReq = webClient.request(
|
|
ctx.request().method(), port, host, targetPathFinal
|
|
);
|
|
|
|
ctx.request().headers().forEach(header -> {
|
|
if (!"host".equalsIgnoreCase(header.getKey())) {
|
|
proxyReq.putHeader(header.getKey(), header.getValue());
|
|
}
|
|
});
|
|
proxyReq.putHeader("Host", host + ":" + port);
|
|
|
|
ctx.request().bodyHandler(body -> {
|
|
if (body != null && body.length() > 0) {
|
|
proxyReq.sendBuffer(body)
|
|
.onSuccess(resp -> sendResponse(ctx, resp))
|
|
.onFailure(err -> sendError(ctx, err));
|
|
} else {
|
|
proxyReq.send()
|
|
.onSuccess(resp -> sendResponse(ctx, resp))
|
|
.onFailure(err -> sendError(ctx, err));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
private void sendResponse(RoutingContext ctx, HttpResponse<Buffer> resp) {
|
|
ctx.response().setStatusCode(resp.statusCode());
|
|
resp.headers().forEach(h -> ctx.response().putHeader(h.getKey(), h.getValue()));
|
|
ctx.response().end(resp.body());
|
|
}
|
|
|
|
private void sendError(RoutingContext ctx, Throwable err) {
|
|
log.error("Proxy error: {}", err.getMessage());
|
|
ctx.response().setStatusCode(502).end("Bad Gateway: " + err.getMessage());
|
|
}
|
|
|
|
private Router initRouter(SessionHandler sessionHandler) {
|
|
|
|
Router router = Router.router(vertx);
|
|
|
|
router.route().handler(ctx -> {
|
|
String path = ctx.request().path();
|
|
if (path != null && path.startsWith(config.pma.basePath + "/")) {
|
|
ctx.next(); // пропускаем BodyHandler для прокси
|
|
} else {
|
|
BodyHandler.create().handle(ctx);
|
|
}
|
|
});
|
|
router.route().handler(sessionHandler);
|
|
|
|
setupPhpmyadminProxy(router);
|
|
|
|
SecurityHandler securityHandlers = new SecurityHandler(settingsService);
|
|
|
|
// Обработчики безопасности
|
|
router.route().handler(securityHandlers.hostValidator());
|
|
router.route().handler(securityHandlers.proxyHeadersHandler());
|
|
router.route().handler(securityHandlers.cspHeader());
|
|
|
|
// 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();
|
|
}
|
|
});
|
|
|
|
router.route().handler(ctx -> {
|
|
long start = System.currentTimeMillis();
|
|
String method = ctx.request().method().name();
|
|
String path = ctx.request().path();
|
|
final String remoteIp = ctx.get("realClientIp") != null ?
|
|
ctx.get("realClientIp") :
|
|
ctx.request().remoteAddress().host();
|
|
ctx.addBodyEndHandler(v -> {
|
|
long duration = System.currentTimeMillis() - start;
|
|
log.info("{} {} - {} ms - {} - {}",
|
|
method, path, duration, ctx.response().getStatusCode(), remoteIp);
|
|
});
|
|
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");
|
|
}
|
|
});
|
|
|
|
// Rate Limiter Handler
|
|
RedisRateLimiter limiter = new RedisRateLimiter(
|
|
redis.getRedis(), 60, 60_000
|
|
);
|
|
|
|
router.route().handler(limiter);
|
|
|
|
// 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 -> settingsService.get("enable_registration").onComplete(regCheck -> {
|
|
if (regCheck.succeeded() && "false".equals(regCheck.result())) {
|
|
rc.response().setStatusCode(403).end(new JsonObject().put("error", "Registration is disabled").encode());
|
|
return;
|
|
}
|
|
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/profile").handler(authHandler::requireAuth);
|
|
router.get("/api/profile").handler(rc -> {
|
|
Integer userId = rc.session().get("userId");
|
|
userService.getProfile(userId)
|
|
.onSuccess(profile -> rc.response().putHeader("Content-Type", "application/json").end(profile.encode()))
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
router.put("/api/profile").handler(rc -> {
|
|
Integer userId = rc.session().get("userId");
|
|
JsonObject body = rc.body().asJsonObject();
|
|
String email = body.getString("email");
|
|
String password = body.getString("password");
|
|
String language = body.getString("language");
|
|
userService.updateProfile(userId, email, password, language)
|
|
.onSuccess(v -> {
|
|
if (language != null) rc.session().put("language", language);
|
|
rc.response().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.route("/api/admin/users*").handler(AdminHandler::requireAdmin);
|
|
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 role = body.getString("role");
|
|
String ip = rc.request().remoteAddress().host();
|
|
if (login == null || email == null || password == null) {
|
|
rc.response().setStatusCode(400).end("Missing login, email or password");
|
|
return;
|
|
}
|
|
if (role == null || role.isEmpty()) role = "user";
|
|
userService.createUser(login, email, password, ip, true, role)
|
|
.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 role = body.getString("role");
|
|
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, role)
|
|
.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").getFirst());
|
|
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/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.get("/api/admin/restaurants/:id/check").handler(rc -> {
|
|
int id = Integer.parseInt(rc.pathParam("id"));
|
|
restaurantService.findById(id)
|
|
.onSuccess(rest -> {
|
|
if (rest == null) {
|
|
rc.response().setStatusCode(404).end();
|
|
} else {
|
|
IikoOlapClient iiko = new IikoOlapClient(vertx, rest);
|
|
|
|
iiko.checkConnection()
|
|
.onSuccess(res -> rc.response().putHeader("Content-Type", "application/json").end(res.encode()))
|
|
.onFailure(err -> rc.response().putHeader("Content-Type", "application/json").end(
|
|
new JsonObject().put("success", false).put("error", err.getMessage()).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");
|
|
boolean https = body.getBoolean("https", false);
|
|
if (name == null || login == null || password == null || host == null) {
|
|
rc.response().setStatusCode(400).end("Missing fields");
|
|
return;
|
|
}
|
|
restaurantService.createRestaurant(name, login, password, host, https)
|
|
.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");
|
|
boolean https = body.getBoolean("https", false);
|
|
if (name == null || login == null || host == null) {
|
|
rc.response().setStatusCode(400).end("Missing required fields");
|
|
return;
|
|
}
|
|
restaurantService.updateRestaurant(id, name, login, password, host, https)
|
|
.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.getPublicSettings()
|
|
.onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode()))
|
|
.onFailure(err -> rc.response().setStatusCode(500).end());
|
|
});
|
|
|
|
router.route("/api/admin/settings*").handler(AdminHandler::requireAdmin);
|
|
router.get("/api/admin/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/admin/settings").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()));
|
|
});
|
|
|
|
externalDataBaseService.handleRoute(router);
|
|
|
|
new IikoHandler(vertx, router, db, restaurantService, authHandler);
|
|
|
|
|
|
// Роуты для OLAP запросов
|
|
router.get("/api/olap/queries").handler(authHandler::requireAuth).handler(rc -> {
|
|
olapQueryService.getAllQueries()
|
|
.onSuccess(queries -> rc.response().putHeader("Content-Type", "application/json").end(queries.encode()))
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
|
|
router.get("/api/olap/queries/:id").handler(authHandler::requireAuth).handler(rc -> {
|
|
int id = Integer.parseInt(rc.pathParam("id"));
|
|
olapQueryService.getQueryById(id)
|
|
.onSuccess(query -> {
|
|
if (query == null) rc.response().setStatusCode(404).end();
|
|
else rc.response().putHeader("Content-Type", "application/json").end(query.encode());
|
|
})
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
|
|
router.post("/api/olap/queries").handler(authHandler::requireAuth).handler(rc -> {
|
|
JsonObject body = rc.body().asJsonObject();
|
|
String name = body.getString("name");
|
|
Integer dbConnectionId = body.getInteger("dbConnectionId");
|
|
JsonObject config = body.getJsonObject("config");
|
|
JsonArray restaurantIdsArray = body.getJsonArray("restaurantIds", new JsonArray());
|
|
List<Integer> restaurantIds = restaurantIdsArray.stream().map(id -> (Integer) id).collect(Collectors.toList());
|
|
|
|
// Получаем active: сначала из тела, иначе из конфига, иначе true
|
|
Boolean active = body.getBoolean("active");
|
|
if (active == null && config != null) active = config.getBoolean("active", true);
|
|
if (active == null) active = true;
|
|
|
|
if (name == null || dbConnectionId == null || config == null) {
|
|
rc.response().setStatusCode(400).end("Missing required fields");
|
|
return;
|
|
}
|
|
|
|
String tableName = config.getString("tableName");
|
|
|
|
if (tableName.isEmpty()) {
|
|
rc.response().setStatusCode(400).end("Missing required fields");
|
|
return;
|
|
}
|
|
|
|
if (!olapQueryService.isValidTableName(tableName)) {
|
|
rc.response().setStatusCode(400).end("Invalid tableName: must start with a letter and contain only letters and digits");
|
|
return;
|
|
}
|
|
|
|
Boolean finalActive = active;
|
|
olapQueryService.generateSql(config, dbConnectionId)
|
|
.compose(sql -> olapQueryService.createQuery(name, dbConnectionId, config, restaurantIds, sql, finalActive))
|
|
.onSuccess(id -> rc.response().setStatusCode(201).putHeader("Content-Type", "application/json").end(new JsonObject().put("id", id).encode()))
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
|
|
router.put("/api/olap/queries/:id").handler(authHandler::requireAuth).handler(rc -> {
|
|
int id = Integer.parseInt(rc.pathParam("id"));
|
|
JsonObject body = rc.body().asJsonObject();
|
|
String name = body.getString("name");
|
|
Integer dbConnectionId = body.getInteger("dbConnectionId");
|
|
JsonObject config = body.getJsonObject("config");
|
|
JsonArray restaurantIdsArray = body.getJsonArray("restaurantIds", new JsonArray());
|
|
List<Integer> restaurantIds = restaurantIdsArray.stream().map(v -> (Integer) v).collect(Collectors.toList());
|
|
|
|
Boolean active = body.getBoolean("active");
|
|
if (active == null && config != null) active = config.getBoolean("active", true);
|
|
if (active == null) active = true;
|
|
|
|
if (name == null || dbConnectionId == null || config == null) {
|
|
rc.response().setStatusCode(400).end("Missing required fields");
|
|
return;
|
|
}
|
|
|
|
String tableName = config.getString("tableName");
|
|
|
|
if (tableName.isEmpty()) {
|
|
rc.response().setStatusCode(400).end("Missing required fields");
|
|
return;
|
|
}
|
|
|
|
if (!olapQueryService.isValidTableName(tableName)) {
|
|
rc.response().setStatusCode(400).end("Invalid tableName: must start with a letter and contain only letters and digits");
|
|
return;
|
|
}
|
|
|
|
Boolean finalActive = active;
|
|
olapQueryService.generateSql(config, dbConnectionId)
|
|
.compose(sql -> olapQueryService.updateQuery(id, name, dbConnectionId, config, restaurantIds, sql, finalActive))
|
|
.onSuccess(v -> rc.response().end())
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
|
|
router.delete("/api/olap/queries/:id").handler(authHandler::requireAuth).handler(rc -> {
|
|
int id = Integer.parseInt(rc.pathParam("id"));
|
|
olapQueryService.deleteQuery(id)
|
|
.onSuccess(v -> rc.response().end())
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
|
|
router.post("/api/olap/generate-sql").handler(authHandler::requireAuth).handler(rc -> {
|
|
JsonObject body = rc.body().asJsonObject();
|
|
JsonObject config = body.getJsonObject("config");
|
|
Integer dbConnectionId = body.getInteger("dbConnectionId");
|
|
if (config == null || dbConnectionId == null) {
|
|
rc.response().setStatusCode(400).end("Missing config or dbConnectionId");
|
|
return;
|
|
}
|
|
olapQueryService.generateSql(config, dbConnectionId)
|
|
.onSuccess(sql -> rc.response().putHeader("Content-Type", "text/plain").end(sql))
|
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
|
});
|
|
|
|
router.post("/api/olap/export-json").handler(authHandler::requireAuth).handler(rc -> {
|
|
JsonObject body = rc.body().asJsonObject();
|
|
JsonObject config = body.getJsonObject("config");
|
|
if (config == null) {
|
|
rc.response().setStatusCode(400).end("Missing config");
|
|
return;
|
|
}
|
|
JsonObject fullJson = olapQueryService.generateFullIikoJson(config);
|
|
rc.response()
|
|
.putHeader("Content-Type", "application/json")
|
|
.putHeader("Content-Disposition", "attachment; filename=olap_export.json")
|
|
.end(fullJson.encodePrettily());
|
|
});
|
|
|
|
return router;
|
|
}
|
|
|
|
private void startHttp(Router router, Promise<Void> startPromise) {
|
|
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()));
|
|
}
|
|
}
|