2 Commits
main ... dev

110 changed files with 2246 additions and 2446 deletions

View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma.api</groupId>
<artifactId>backlib</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
@@ -24,11 +24,11 @@
<distributionManagement>
<repository>
<id>gitea</id>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
<snapshotRepository>
<id>gitea</id>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</snapshotRepository>
</distributionManagement>

View File

@@ -1,469 +0,0 @@
package net.miarma.api.backlib;
import java.time.LocalDateTime;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.vertx.core.json.JsonObject;
import net.miarma.api.backlib.gson.APIDontReturnExclusionStrategy;
import net.miarma.api.backlib.gson.JsonObjectTypeAdapter;
import net.miarma.api.backlib.gson.LocalDateTimeAdapter;
import net.miarma.api.backlib.gson.ValuableEnumDeserializer;
import net.miarma.api.backlib.gson.ValuableEnumTypeAdapter;
import net.miarma.api.backlib.interfaces.IUserRole;
/**
* Clase que contiene constantes y enumeraciones utilizadas en la API de MiarmaCore.
* @author José Manuel Amador Gallardo
*/
public class Constants {
public static final String APP_NAME = "MiarmaCoreAPI";
public static final String BASE_PREFIX = "/api";
public static final String CORE_PREFIX = BASE_PREFIX + "/core/v1"; // tabla de usuarios central
public static final String AUTH_PREFIX = "/auth/v1";
public static final String HUERTOS_PREFIX = BASE_PREFIX + "/huertos/v1";
public static final String MMC_PREFIX = BASE_PREFIX + "/mmc/v1";
public static final String CINE_PREFIX = BASE_PREFIX + "/cine/v1";
public static final String MPASTE_PREFIX = BASE_PREFIX + "/mpaste/v1";
public static final String AUTH_EVENT_BUS = "auth.eventbus";
public static final String CORE_EVENT_BUS = "core.eventbus";
public static final String HUERTOS_EVENT_BUS = "huertos.eventbus";
public static final String MMC_EVENT_BUS = "mmc.eventbus";
public static final String CINE_EVENT_BUS = "cine.eventbus";
public static final String MPASTE_EVENT_BUS = "mpaste.eventbus";
public static final List<String> HUERTOS_ALLOWED_FOLDERS =
List.of("INBOX", "Drafts", "Sent", "Spam", "Trash");
public static final Logger LOGGER = LoggerFactory.getLogger(Constants.APP_NAME);
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
.registerTypeAdapter(JsonObject.class, new JsonObjectTypeAdapter())
.registerTypeHierarchyAdapter(ValuableEnum.class, new ValuableEnumTypeAdapter())
.registerTypeAdapter(CoreUserGlobalStatus.class, new ValuableEnumDeserializer())
.registerTypeAdapter(CoreUserRole.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosUserType.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosUserStatus.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosUserRole.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosRequestStatus.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosRequestType.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosPaymentType.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosPaymentFrequency.class, new ValuableEnumDeserializer())
.registerTypeAdapter(HuertosAnnouncePriority.class, new ValuableEnumDeserializer())
.registerTypeAdapter(MMCUserStatus.class, new ValuableEnumDeserializer())
.registerTypeAdapter(MMCUserRole.class, new ValuableEnumDeserializer())
.registerTypeAdapter(CoreFileContext.class, new ValuableEnumDeserializer())
.registerTypeAdapter(MMCModStatus.class, new ValuableEnumDeserializer())
.registerTypeAdapter(CineUserRole.class, new ValuableEnumDeserializer())
.registerTypeAdapter(CineUserStatus.class, new ValuableEnumDeserializer())
.addSerializationExclusionStrategy(new APIDontReturnExclusionStrategy())
.create();
public enum CoreUserRole implements IUserRole {
USER(0),
ADMIN(1);
private final int value;
CoreUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CoreUserRole fromInt(int i) {
for (CoreUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid CoreUserRole value: " + i);
}
}
public enum CoreUserGlobalStatus implements ValuableEnum {
INACTIVE(0),
ACTIVE(1);
private final int value;
CoreUserGlobalStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CoreUserGlobalStatus fromInt(int i) {
for (CoreUserGlobalStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid CoreUserGlobalStatus value: " + i);
}
}
public enum CoreFileContext implements ValuableEnum {
CORE(0),
HUERTOS(1),
MMC(2),
CINE(3);
private final int value;
CoreFileContext(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public String toCtxString() {
return switch(this) {
case CORE -> "core";
case HUERTOS -> "huertos";
case MMC -> "miarmacraft";
case CINE -> "cine";
};
}
public static CoreFileContext fromInt(int i) {
for (CoreFileContext context : values()) {
if (context.value == i) return context;
}
throw new IllegalArgumentException("Invalid CoreFileContext value: " + i);
}
}
public enum HuertosUserRole implements IUserRole {
USER(0),
ADMIN(1),
DEV(2);
private final int value;
HuertosUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosUserRole fromInt(int i) {
for (HuertosUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid HuertosUserRole value: " + i);
}
}
public enum HuertosUserType implements ValuableEnum {
WAIT_LIST(0),
MEMBER(1),
WITH_GREENHOUSE(2),
COLLABORATOR(3),
DEVELOPER(5),
SUBSIDY(4);
private final int value;
HuertosUserType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosUserType fromInt(int i) {
for (HuertosUserType type : values()) {
if (type.value == i) return type;
}
throw new IllegalArgumentException("Invalid HuertosUserType value: " + i);
}
}
public enum HuertosUserStatus implements ValuableEnum {
INACTIVE(0),
ACTIVE(1);
private final int value;
HuertosUserStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosUserStatus fromInt(int i) {
for (HuertosUserStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid HuertosUserStatus value: " + i);
}
}
public enum HuertosRequestStatus implements ValuableEnum {
PENDING(0),
APPROVED(1),
REJECTED(2);
private final int value;
HuertosRequestStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosRequestStatus fromInt(int i) {
for (HuertosRequestStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid HuertoRequestStatus value: " + i);
}
}
public enum HuertosPaymentType implements ValuableEnum {
BANK(0),
CASH(1);
private final int value;
HuertosPaymentType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosPaymentType fromInt(int i) {
for (HuertosPaymentType type : values()) {
if (type.value == i) return type;
}
throw new IllegalArgumentException("Invalid HuertoPaymentType value: " + i);
}
}
public enum HuertosRequestType implements ValuableEnum {
REGISTER(0),
UNREGISTER(1),
ADD_COLLABORATOR(2),
REMOVE_COLLABORATOR(3),
ADD_GREENHOUSE(4),
REMOVE_GREENHOUSE(5);
private final int value;
HuertosRequestType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosRequestType fromInt(int i) {
for (HuertosRequestType type : values()) {
if (type.value == i) return type;
}
throw new IllegalArgumentException("Invalid HuertoRequestType value: " + i);
}
}
public enum HuertosAnnouncePriority implements ValuableEnum {
LOW(0),
MEDIUM(1),
HIGH(2);
private final int value;
HuertosAnnouncePriority(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosAnnouncePriority fromInt(int i) {
for (HuertosAnnouncePriority priority : values()) {
if (priority.value == i) return priority;
}
throw new IllegalArgumentException("Invalid HuertoAnnouncePriority value: " + i);
}
}
public enum HuertosPaymentFrequency implements ValuableEnum {
BIYEARLY(0),
YEARLY(1);
private final int value;
HuertosPaymentFrequency(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosPaymentFrequency fromInt(int i) {
for (HuertosPaymentFrequency frequency : values()) {
if (frequency.value == i) return frequency;
}
throw new IllegalArgumentException("Invalid HuertoPaymentFrequency value: " + i);
}
}
public enum MMCUserRole implements IUserRole {
PLAYER(0),
ADMIN(1);
private final int value;
MMCUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static MMCUserRole fromInt(int i) {
for (MMCUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid MMCUserRole value: " + i);
}
}
public enum MMCUserStatus implements ValuableEnum {
INACTIVE(0),
ACTIVE(1);
private final int value;
MMCUserStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static MMCUserStatus fromInt(int i) {
for (MMCUserStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid MMCUserStatus value: " + i);
}
}
public enum MMCModStatus implements ValuableEnum {
ACTIVE(0),
INACTIVE(1);
private final int value;
MMCModStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static MMCModStatus fromInt(int i) {
for (MMCModStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid MiarmacraftModStatus value: " + i);
}
}
public enum CineUserStatus implements ValuableEnum {
ACTIVE(1),
INACTIVE(0);
private final int value;
CineUserStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CineUserStatus fromInt(int i) {
for (CineUserStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid CineUserStatus value: " + i);
}
}
public enum CineUserRole implements IUserRole {
USER(0),
ADMIN(1);
private final int value;
CineUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CineUserRole fromInt(int i) {
for (CineUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid CineUserRole value: " + i);
}
}
// Private constructor to prevent instantiation
private Constants() {
throw new AssertionError("Utility class cannot be instantiated.");
}
}

View File

@@ -1,4 +1,4 @@
package net.miarma.api.backlib;
package net.miarma.api.backlib.config;
import java.io.File;
import java.io.FileInputStream;
@@ -8,6 +8,10 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import org.slf4j.Logger;
import net.miarma.api.backlib.log.LoggerProvider;
/**
* Gestión de toda la configuración de la aplicación.
* Se encarga de cargar, guardar y proporcionar acceso a las propiedades de configuración.
@@ -19,14 +23,16 @@ import java.util.Properties;
*/
public class ConfigManager {
private static ConfigManager instance;
private final String appName;
private final File configFile;
private final Properties config;
private static final String CONFIG_FILE_NAME = "config.properties";
private final Logger LOGGER = LoggerProvider.getLogger();
private ConfigManager() {
String path = getBaseDir() + CONFIG_FILE_NAME;
this.configFile = new File(path);
this.appName = System.getenv().getOrDefault("APP_NAME", "miarma-backend");
this.config = new Properties();
this.configFile = new File(getBaseDir() + CONFIG_FILE_NAME);
}
public static synchronized ConfigManager getInstance() {
@@ -41,7 +47,7 @@ public class ConfigManager {
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
config.load(isr);
} catch (IOException e) {
Constants.LOGGER.error("Error loading configuration file: ", e);
LOGGER.error("Error loading configuration file: ", e);
}
}
public File getConfigFile() {
@@ -118,6 +124,11 @@ public class ConfigManager {
return Boolean.parseBoolean(System.getenv("RUNNING_IN_DOCKER"));
}
public String getApiPrefix(String domain) {
return getStringProperty("api." + domain + ".prefix").replace("${app.version}",
String.valueOf(getStringProperty("app.version")));
}
public String getStringProperty(String key) {
return config.getProperty(key);
}
@@ -138,9 +149,9 @@ public class ConfigManager {
private void saveConfig() {
try (FileOutputStream fos = new FileOutputStream(configFile)) {
config.store(fos, "Configuration for: " + Constants.APP_NAME);
config.store(fos, "Configuration for: " + this.appName);
} catch (IOException e) {
Constants.LOGGER.error("Error saving configuration file: ", e);
LOGGER.error("Error saving configuration file: ", e);
}
}
}

View File

@@ -1,4 +1,4 @@
package net.miarma.api.backlib;
package net.miarma.api.backlib.config;
/**
* Enum que representa los diferentes tipos de sistemas operativos soportados

View File

@@ -1,83 +0,0 @@
package net.miarma.api.backlib.core.entities;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.Constants.CoreFileContext;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.db.AbstractEntity;
import java.time.LocalDateTime;
@Table("files")
public class FileEntity extends AbstractEntity {
private Integer file_id;
private String file_name;
private String file_path;
private String mime_type;
private Integer uploaded_by;
private CoreFileContext context;
private LocalDateTime uploaded_at;
public FileEntity() {
super();
}
public FileEntity(Row row) {
super(row);
}
public Integer getFile_id() {
return file_id;
}
public void setFile_id(Integer file_id) {
this.file_id = file_id;
}
public String getFile_name() {
return file_name;
}
public void setFile_name(String file_name) {
this.file_name = file_name;
}
public String getFile_path() {
return file_path;
}
public void setFile_path(String file_path) {
this.file_path = file_path;
}
public String getMime_type() {
return mime_type;
}
public void setMime_type(String mime_type) {
this.mime_type = mime_type;
}
public Integer getUploaded_by() {
return uploaded_by;
}
public void setUploaded_by(Integer uploaded_by) {
this.uploaded_by = uploaded_by;
}
public CoreFileContext getContext() {
return context;
}
public void setContext(CoreFileContext context) {
this.context = context;
}
public LocalDateTime getUploaded_at() {
return uploaded_at;
}
public void setUploaded_at(LocalDateTime uploaded_at) {
this.uploaded_at = uploaded_at;
}
}

View File

@@ -1,63 +0,0 @@
package net.miarma.api.backlib.core.entities;
import java.time.LocalDateTime;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.Constants.CoreUserGlobalStatus;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.annotations.APIDontReturn;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.db.AbstractEntity;
import net.miarma.api.backlib.interfaces.IUser;
@Table("users")
public class UserEntity extends AbstractEntity implements IUser {
private Integer user_id;
private String user_name;
private String email;
private String display_name;
@APIDontReturn
private String password;
private String avatar;
private CoreUserGlobalStatus global_status;
private CoreUserRole role;
private LocalDateTime created_at;
private LocalDateTime updated_at;
public UserEntity() { }
public UserEntity(Row row) { super(row); }
public Integer getUser_id() { return user_id; }
public void setUser_id(Integer user_id) { this.user_id = user_id; }
public String getUser_name() { return user_name; }
public void setUser_name(String user_name) { this.user_name = user_name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getDisplay_name() { return display_name; }
public void setDisplay_name(String display_name) { this.display_name = display_name; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getAvatar() { return avatar; }
public void setAvatar(String avatar) { this.avatar = avatar; }
public CoreUserGlobalStatus getGlobal_status() { return global_status; }
public void setGlobal_status(CoreUserGlobalStatus global_status) { this.global_status = global_status; }
public CoreUserRole getGlobal_role() { return role; }
public void setGlobal_role(CoreUserRole role) { this.role = role; }
public LocalDateTime getCreated_at() { return created_at; }
public void setCreated_at(LocalDateTime created_at) { this.created_at = created_at; }
public LocalDateTime getUpdated_at() { return updated_at; }
public void setUpdated_at(LocalDateTime updated_at) { this.updated_at = updated_at; }
public static UserEntity from(IUser user) {
UserEntity entity = new UserEntity();
entity.setUser_id(user.getUser_id());
entity.setUser_name(user.getUser_name());
entity.setDisplay_name(user.getDisplay_name());
entity.setEmail(user.getEmail());
entity.setPassword(user.getPassword());
entity.setAvatar(user.getAvatar());
entity.setGlobal_status(user.getGlobal_status());
entity.setGlobal_role(user.getGlobal_role());
return entity;
}
}

View File

@@ -1,70 +0,0 @@
package net.miarma.api.backlib.core.handlers;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.backlib.core.services.UserService;
@SuppressWarnings("unused")
public class UserDataHandler {
private final UserService userService;
public UserDataHandler(Pool pool) {
this.userService = new UserService(pool);
}
public void getAll(RoutingContext ctx) {
QueryParams params = QueryParams.from(ctx);
userService.getAll(params)
.onSuccess(users -> JsonUtil.sendJson(ctx, ApiStatus.OK, users)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
public void getById(RoutingContext ctx) {
Integer userId = Integer.parseInt(ctx.pathParam("user_id"));
userService.getById(userId)
.onSuccess(user -> JsonUtil.sendJson(ctx, ApiStatus.OK, user)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
public void create(RoutingContext ctx) {
UserEntity user = Constants.GSON.fromJson(ctx.body().asString(), UserEntity.class);
userService.register(user)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
public void update(RoutingContext ctx) {
UserEntity user = Constants.GSON.fromJson(ctx.body().asString(), UserEntity.class);
userService.update(user)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, result)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
public void delete(RoutingContext ctx) {
Integer userId = Integer.parseInt(ctx.pathParam("user_id"));
userService.delete(userId)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, result)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
}

View File

@@ -1,286 +0,0 @@
package net.miarma.api.backlib.core.handlers;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.backlib.core.entities.UserEntity;
public class UserLogicHandler {
private final Vertx vertx;
public UserLogicHandler(Vertx vertx) {
this.vertx = vertx;
}
public void login(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "login")
.put("email", body.getString("email", null))
.put("userName", body.getString("userName", null))
.put("password", body.getString("password"))
.put("keepLoggedIn", body.getBoolean("keepLoggedIn", false));
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonObject result = (JsonObject) ar.result().body();
result.put("tokenTime", System.currentTimeMillis());
JsonUtil.sendJson(ctx, ApiStatus.OK, result);
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void loginValidate(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "loginValidate")
.put("userId", body.getInteger("userId"))
.put("password", body.getString("password"));
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void register(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "register")
.put("userName", body.getString("userName"))
.put("email", body.getString("email"))
.put("displayName", body.getString("displayName"))
.put("password", body.getString("password"));
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.CREATED, null);
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void changePassword(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "changePassword")
.put("userId", body.getInteger("userId"))
.put("newPassword", body.getString("newPassword"));
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, true, "Updated");
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void validateToken(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
JsonObject request = new JsonObject()
.put("action", "validateToken")
.put("token", token);
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded() && Boolean.TRUE.equals(ar.result().body())) {
JsonUtil.sendJson(ctx, ApiStatus.OK, true, "Valid token");
} else {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, false, "Invalid token");
}
});
} else {
JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Missing or invalid Authorization header");
}
}
public void refreshToken(RoutingContext ctx) {
String tokenHeader = ctx.request().getHeader("Authorization");
if (tokenHeader == null || !tokenHeader.startsWith("Bearer ")) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Missing or invalid Authorization header");
return;
}
String token = tokenHeader.substring("Bearer ".length());
JWTManager jwt = JWTManager.getInstance();
try {
DecodedJWT decoded = jwt.decodeWithoutVerification(token);
int userId = decoded.getClaim("userId").asInt();
if (userId == -1) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token");
return;
}
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, new JsonObject()
.put("action", "getUserById")
.put("userId", userId), ar -> {
if (ar.succeeded()) {
JsonObject userJson = (JsonObject) ar.result().body();
UserEntity user = Constants.GSON.fromJson(userJson.encode(), UserEntity.class);
String newToken = jwt.generateToken(user.getUser_name(), user.getUser_id(), user.getGlobal_role(), false);
JsonUtil.sendJson(ctx, ApiStatus.OK, new JsonObject().put("token", newToken));
} else {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "User not found");
}
});
} catch (Exception e) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token");
}
}
public void getInfo(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Unauthorized");
return;
}
String token = authHeader.substring(7);
int userId = net.miarma.api.backlib.security.JWTManager.getInstance().getUserId(token);
if (userId <= 0) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token");
return;
}
JsonObject request = new JsonObject()
.put("action", "getInfo")
.put("userId", userId);
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void exists(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "userExists")
.put("userId", userId);
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void getStatus(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "getStatus")
.put("userId", userId);
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void getRole(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "getRole")
.put("userId", userId);
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void getAvatar(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "getAvatar")
.put("userId", userId);
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void updateStatus(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "updateStatus")
.put("userId", body.getInteger("userId"))
.put("status", body.getInteger("status"));
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null);
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void updateRole(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "updateRole")
.put("userId", body.getInteger("userId"))
.put("role", body.getInteger("role"));
vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null);
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
}

View File

@@ -1,209 +0,0 @@
package net.miarma.api.backlib.core.services;
import java.util.List;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.Constants.CoreUserGlobalStatus;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.exceptions.AlreadyExistsException;
import net.miarma.api.backlib.exceptions.BadRequestException;
import net.miarma.api.backlib.exceptions.ForbiddenException;
import net.miarma.api.backlib.exceptions.NotFoundException;
import net.miarma.api.backlib.exceptions.UnauthorizedException;
import net.miarma.api.backlib.exceptions.ValidationException;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.security.PasswordHasher;
import net.miarma.api.backlib.core.dao.UserDAO;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.backlib.core.validators.UserValidator;
public class UserService {
private final UserDAO userDAO;
private final UserValidator userValidator;
public UserService(Pool pool) {
this.userDAO = new UserDAO(pool);
this.userValidator = new UserValidator();
}
/* AUTHENTICATION */
public Future<JsonObject> login(String emailOrUsername, String plainPassword, boolean keepLoggedIn) {
return getByEmail(emailOrUsername).compose(user -> {
if (user == null) {
return getByUserName(emailOrUsername).compose(user2 -> {
if (user2 == null) {
return Future.succeededFuture(null);
}
return Future.succeededFuture(user2);
});
}
return Future.succeededFuture(user);
}).compose(user -> {
if (user == null) {
return Future.failedFuture(new BadRequestException("Invalid credentials"));
}
if (user.getGlobal_status() != Constants.CoreUserGlobalStatus.ACTIVE) {
return Future.failedFuture(new ForbiddenException("User is not active"));
}
if (!PasswordHasher.verify(plainPassword, user.getPassword())) {
return Future.failedFuture(new BadRequestException("Invalid credentials"));
}
JWTManager jwtManager = JWTManager.getInstance();
String token = jwtManager.generateToken(user.getUser_name(), user.getUser_id(), user.getGlobal_role(), keepLoggedIn);
JsonObject response = new JsonObject()
.put("token", token)
.put("loggedUser", new JsonObject(user.encode()));
return Future.succeededFuture(response);
});
}
public Future<JsonObject> loginValidate(Integer userId, String password) {
return getById(userId).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found"));
}
if (!PasswordHasher.verify(password, user.getPassword())) {
return Future.failedFuture(new BadRequestException("Invalid credentials"));
}
JsonObject response = new JsonObject()
.put("valid", true);
return Future.succeededFuture(response);
});
}
public Future<UserEntity> register(UserEntity user) {
return getByEmail(user.getEmail()).compose(existing -> {
if (existing != null) {
return Future.failedFuture(new AlreadyExistsException("Email already exists"));
}
user.setPassword(PasswordHasher.hash(user.getPassword()));
user.setGlobal_role(CoreUserRole.USER);
user.setGlobal_status(CoreUserGlobalStatus.ACTIVE);
return userValidator.validate(user).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
return userDAO.insert(user);
});
});
}
public Future<UserEntity> changePassword(int userId, String newPassword) {
return getById(userId).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found"));
}
user.setPassword(PasswordHasher.hash(newPassword));
return userDAO.update(user);
});
}
public Future<Boolean> validateToken(String token) {
JWTManager jwtManager = JWTManager.getInstance();
return jwtManager.isValid(token) ?
Future.succeededFuture(true) :
Future.failedFuture(new UnauthorizedException("Invalid token"));
}
/* USERS OPERATIONS */
public Future<List<UserEntity>> getAll(QueryParams params) {
return userDAO.getAll(params);
}
public Future<UserEntity> getById(Integer id) {
return userDAO.getById(id).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found in the database"));
}
return Future.succeededFuture(user);
});
}
public Future<UserEntity> getByEmail(String email) {
return userDAO.getByEmail(email);
}
public Future<UserEntity> getByUserName(String userName) {
return userDAO.getByUserName(userName);
}
public Future<UserEntity> updateRole(Integer userId, CoreUserRole role) {
return getById(userId).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found in the database"));
}
user.setGlobal_role(role);
return userDAO.update(user);
});
}
public Future<UserEntity> updateStatus(Integer userId, CoreUserGlobalStatus status) {
return getById(userId).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found in the database"));
}
user.setGlobal_status(status);
return userDAO.update(user);
});
}
/* CRUD OPERATIONS */
public Future<UserEntity> create(UserEntity user) {
return userValidator.validate(user).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
return userDAO.insert(user);
});
}
public Future<UserEntity> update(UserEntity user) {
return userValidator.validate(user).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
if (user.getPassword() == null || user.getPassword().isEmpty()) {
user.setPassword(null);
}
return userDAO.update(user);
});
}
public Future<UserEntity> upsert(UserEntity user) {
return userValidator.validate(user).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
if (user.getPassword() != null && !user.getPassword().isEmpty()) {
user.setPassword(PasswordHasher.hash(user.getPassword()));
}
return userDAO.upsert(user, "user_id", "email", "user_name");
});
}
public Future<Boolean> delete(Integer id) {
return getById(id).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found in the database"));
}
return userDAO.delete(id);
});
}
}

View File

@@ -1,84 +0,0 @@
package net.miarma.api.backlib.core.validators;
import io.vertx.core.Future;
import net.miarma.api.backlib.validation.ValidationResult;
import net.miarma.api.backlib.core.entities.FileEntity;
public class FileValidator {
public Future<ValidationResult> validate(FileEntity file, int size) {
ValidationResult result = new ValidationResult();
if (file == null) {
return Future.succeededFuture(result.addError("file", "El archivo no puede ser nulo"));
}
if (file.getFile_name() == null || file.getFile_name().isBlank()) {
result.addError("file_name", "El nombre del archivo es obligatorio");
}
if (file.getMime_type() == null || file.getMime_type().isBlank()) {
result.addError("mime_type", "El tipo MIME es obligatorio");
}
if (file.getContext() == null) {
result.addError("context", "El contexto del archivo es obligatorio");
}
if (file.getUploaded_by() == null || file.getUploaded_by() <= 0) {
result.addError("uploaded_by", "El ID del usuario que subió el archivo es obligatorio y debe ser válido");
}
if (size <= 0) {
result.addError("size", "El archivo debe pesar más de 0 bytes");
}
if (file.getFile_name() != null && file.getFile_name().length() > 255) {
result.addError("file_name", "El nombre del archivo es demasiado largo");
}
if (size > 10485760) { // 10 MB limit
result.addError("size", "El archivo no puede pesar más de 10 MB");
}
return Future.succeededFuture(result);
}
public Future<ValidationResult> validate(FileEntity file) {
ValidationResult result = new ValidationResult();
if (file == null) {
return Future.succeededFuture(result.addError("file", "File cannot be null"));
}
if (file.getFile_name() == null || file.getFile_name().isBlank()) {
result.addError("file_name", "File name is required");
}
if (file.getFile_path() == null || file.getFile_path().isBlank()) {
result.addError("file_path", "File path is required");
}
if (file.getMime_type() == null || file.getMime_type().isBlank()) {
result.addError("mime_type", "MIME type is required");
}
if (file.getContext() == null) {
result.addError("context", "File context is required");
}
if (file.getUploaded_by() == null || file.getUploaded_by() <= 0) {
result.addError("uploaded_by", "Uploader user ID is required and must be valid");
}
if (file.getFile_name() != null && file.getFile_name().length() > 255) {
result.addError("file_name", "File name is too long");
}
if (file.getFile_path() != null && file.getFile_path().length() > 255) {
result.addError("file_path", "File path is too long");
}
return Future.succeededFuture(result);
}
}

View File

@@ -1,44 +0,0 @@
package net.miarma.api.backlib.core.validators;
import io.vertx.core.Future;
import net.miarma.api.backlib.validation.ValidationResult;
import net.miarma.api.backlib.core.entities.UserEntity;
public class UserValidator {
public Future<ValidationResult> validate(UserEntity user) {
ValidationResult result = new ValidationResult();
if (user == null) {
return Future.succeededFuture(result.addError("user", "El usuario no puede ser nulo"));
}
if (user.getUser_name() == null || user.getUser_name().isBlank()) {
result.addError("user_name", "El nombre de usuario es obligatorio");
}
if (user.getDisplay_name() == null || user.getDisplay_name().isBlank()) {
result.addError("display_name", "El nombre para mostrar es obligatorio");
}
if (user.getEmail() != null && !user.getEmail().isBlank()) {
if (!user.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
result.addError("email", "El correo electrónico no es válido");
}
}
if (user.getPassword() == null || user.getPassword().isBlank()) {
result.addError("password", "La contraseña es obligatoria");
}
if (user.getGlobal_status() == null) {
result.addError("global_status", "El estado global del usuario es obligatorio");
}
if (user.getGlobal_role() == null) {
result.addError("role", "El rol del usuario es obligatorio");
}
return Future.succeededFuture(result);
}
}

View File

@@ -1,64 +1,45 @@
package net.miarma.api.backlib.db;
import java.lang.reflect.Field;
import java.util.UUID;
import com.google.gson.annotations.SerializedName;
import org.slf4j.Logger;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.ValuableEnum;
import net.miarma.api.backlib.annotations.APIDontReturn;
import net.miarma.api.backlib.interfaces.IValuableEnum;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.backlib.util.BufferUtil;
import java.lang.reflect.Field;
/**
* Clase base para todas las entidades persistentes del sistema.
* <p>
* Proporciona utilidades para:
* <ul>
* <li>Construir una entidad a partir de una fila de base de datos ({@link Row})</li>
* <li>Serializar una entidad a {@link JsonObject}</li>
* <li>Generar una representación en texto</li>
* </ul>
*
* Los campos se mapean por reflexión, lo que permite extender fácilmente las entidades
* sin necesidad de escribir lógica de parsing repetitiva.
*
* @author José Manuel Amador Gallardo
*/
public abstract class AbstractEntity {
private final Logger LOGGER = LoggerProvider.getLogger();
/**
* Constructor por defecto. Requerido para instanciación sin datos.
*/
public AbstractEntity() {}
/**
* Constructor que inicializa los campos de la entidad a partir de una fila de base de datos.
*
* @param row Fila SQL proporcionada por Vert.x.
*/
public AbstractEntity(Row row) {
populateFromRow(row);
}
/**
* Rellena los campos del objeto usando reflexión a partir de una {@link Row} de Vert.x.
* Se soportan tipos básicos (String, int, boolean, etc.), enums con método estático {@code fromInt(int)},
* y {@link java.math.BigDecimal} (a través del tipo {@code Numeric} de Vert.x).
* <p>
* Si un tipo no está soportado, se registra un error en el log y se ignora ese campo.
*
* @param row Fila de datos de la que extraer los valores.
*/
private String getColumnName(Field field) {
if (field.isAnnotationPresent(SerializedName.class)) {
return field.getAnnotation(SerializedName.class).value();
}
return field.getName();
}
private void populateFromRow(Row row) {
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
Class<?> type = field.getType();
String name = field.getName();
String columnName = getColumnName(field);
Object value;
if (type.isEnum()) {
Integer intValue = row.getInteger(name);
Integer intValue = row.getInteger(columnName);
if (intValue != null) {
try {
var method = type.getMethod("fromInt", int.class);
@@ -71,42 +52,43 @@ public abstract class AbstractEntity {
}
} else {
value = switch (type.getSimpleName()) {
case "Integer", "int" -> row.getInteger(name);
case "String" -> row.getString(name);
case "double", "Double" -> row.getDouble(name);
case "long", "Long" -> row.getLong(name);
case "boolean", "Boolean" -> row.getBoolean(name);
case "LocalDateTime" -> row.getLocalDateTime(name);
case "BigDecimal" -> {
try {
var numeric = row.get(io.vertx.sqlclient.data.Numeric.class, row.getColumnIndex(name));
yield numeric != null ? numeric.bigDecimalValue() : null;
} catch (Exception e) {
case "Integer", "int" -> row.getInteger(columnName);
case "String" -> row.getString(columnName);
case "double", "Double" -> row.getDouble(columnName);
case "long", "Long" -> row.getLong(columnName);
case "boolean", "Boolean" -> row.getBoolean(columnName);
case "LocalDateTime" -> row.getLocalDateTime(columnName);
case "UUID" -> {
Object raw = row.getValue(columnName);
if (raw instanceof UUID) yield raw;
if (raw instanceof String s) yield UUID.fromString(s);
if (raw instanceof Buffer b) {
yield BufferUtil.uuidFromBuffer(b);
}
yield null;
}
case "BigDecimal" -> {
try {
var numeric = row.get(io.vertx.sqlclient.data.Numeric.class, row.getColumnIndex(columnName));
yield numeric != null ? numeric.bigDecimalValue() : null;
} catch (Exception e) { yield null; }
}
default -> {
Constants.LOGGER.error("Type not supported yet: {} for field {}", type.getName(), name);
LOGGER.error("Type not supported yet: {} for field {}", type.getName(), field.getName());
yield null;
}
};
}
if (value != null) {
field.set(this, value);
}
} catch (Exception e) {
Constants.LOGGER.error("Error populating field {}: {}", field.getName(), e.getMessage());
LOGGER.error("Error populating field {}: {}", field.getName(), e.getMessage());
}
}
}
/**
* Codifica esta entidad como un objeto JSON, omitiendo los campos anotados con {@link APIDontReturn}.
*
* <p>Si un campo implementa {@link ValuableEnum}, se usará su valor en lugar del nombre del enum.</p>
*
* @return Representación JSON de esta entidad.
*/
public String encode() {
JsonObject json = new JsonObject();
Class<?> clazz = this.getClass();
@@ -114,33 +96,25 @@ public abstract class AbstractEntity {
while (clazz != null) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(APIDontReturn.class)) continue;
field.setAccessible(true);
try {
Object value = field.get(this);
if (value instanceof ValuableEnum ve) {
if (value instanceof IValuableEnum ve) {
json.put(field.getName(), ve.getValue());
} else if (value instanceof UUID u) {
json.put(field.getName(), u.toString());
} else {
json.put(field.getName(), value);
}
} catch (IllegalAccessException e) {
Constants.LOGGER.error("Error accessing field {}: {}", field.getName(), e.getMessage());
LOGGER.error("Error accessing field {}: {}", field.getName(), e.getMessage());
}
}
clazz = clazz.getSuperclass();
}
return json.encode();
}
/**
* Devuelve una representación en texto de la entidad, mostrando todos los campos y sus valores.
*
* <p>Útil para logs y debugging.</p>
*
* @return Cadena de texto con el nombre de la clase y todos los campos.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName()).append(" [ ");
@@ -150,11 +124,10 @@ public abstract class AbstractEntity {
try {
sb.append(field.getName()).append("=").append(field.get(this)).append(", ");
} catch (IllegalAccessException e) {
Constants.LOGGER.error("Error stringing field {}: {}", field.getName(), e.getMessage());
LOGGER.error("Error stringing field {}: {}", field.getName(), e.getMessage());
}
}
sb.append("]");
return sb.toString();
}
}

View File

@@ -1,16 +1,18 @@
package net.miarma.api.backlib.db;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import net.miarma.api.backlib.Constants;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import net.miarma.api.backlib.log.LoggerProvider;
/**
* Gestor centralizado de acceso a la base de datos utilizando Vert.x SQL Client.
@@ -23,6 +25,7 @@ import java.util.List;
*/
public class DatabaseManager {
private final Logger LOGGER = LoggerProvider.getLogger();
private static DatabaseManager instance;
private final Pool pool;
@@ -86,7 +89,7 @@ public class DatabaseManager {
results.add(constructor.newInstance(row));
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException
| InvocationTargetException e) {
Constants.LOGGER.error("Error instantiating class: {}", e.getMessage());
LOGGER.error("Error instantiating class: {}", e.getMessage());
}
}
return results;
@@ -116,7 +119,7 @@ public class DatabaseManager {
Constructor<T> constructor = clazz.getConstructor(Row.class);
return constructor.newInstance(row);
} catch (Exception e) {
Constants.LOGGER.error("Error instantiating class: {}", e.getMessage());
LOGGER.error("Error instantiating class: {}", e.getMessage());
}
}
return null; // Si no hay filas

View File

@@ -4,7 +4,7 @@ import io.vertx.core.Vertx;
import io.vertx.mysqlclient.MySQLConnectOptions;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.PoolOptions;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.config.ConfigManager;
/**
* Factoría de {@link Pool} para conexiones MySQL usando Vert.x.

View File

@@ -1,64 +1,42 @@
package net.miarma.api.backlib.db;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.annotations.Table;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* Clase utilitaria para construir queries SQL dinámicamente mediante reflexión,
* usando entidades anotadas con {@link Table}.
* <p>
* Soporta operaciones SELECT, INSERT, UPDATE (con y sin valores nulos), y UPSERT.
* También permite aplicar filtros desde un mapa o directamente desde un objeto.
* <p>
* ¡Ojo! No ejecuta la query, solo la construye.
*
* @author José Manuel Amador Gallardo
*/
import org.slf4j.Logger;
import com.google.gson.annotations.SerializedName;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.log.LoggerProvider;
public class QueryBuilder {
private static final Logger LOGGER = LoggerProvider.getLogger();
private final StringBuilder query;
private String sort;
private String order;
private String limit;
private String orderByClause;
private String limitClause;
private String offsetClause;
private Class<?> entityClass;
public QueryBuilder() {
this.query = new StringBuilder();
}
/**
* Obtiene el nombre de la tabla desde la anotación @Table de la clase dada.
*/
private static <T> String getTableName(Class<T> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("Class cannot be null");
if (clazz == null) throw new IllegalArgumentException("Class cannot be null");
if (!clazz.isAnnotationPresent(Table.class)) throw new IllegalArgumentException("Class does not have @Table annotation");
return clazz.getAnnotation(Table.class).value();
}
if (clazz.isAnnotationPresent(Table.class)) {
Table annotation = clazz.getAnnotation(Table.class);
return annotation.value();
}
throw new IllegalArgumentException("Class does not have @Table annotation");
private static String getColumnName(Field field) {
SerializedName annotation = field.getAnnotation(SerializedName.class);
return annotation != null ? annotation.value() : field.getName();
}
/**
* Devuelve la consulta SQL construida hasta el momento.
*/
public String getQuery() {
return query.toString();
}
/**
* Extrae el valor de un campo, manejando enums y tipos especiales.
* Si es un Enum y tiene getValue(), lo usa; si no, devuelve el name().
* Si es un LocalDateTime, lo convierte a String en formato SQL.
*/
private static Object extractValue(Object fieldValue) {
if (fieldValue == null) return null;
if (fieldValue instanceof Enum<?>) {
try {
var method = fieldValue.getClass().getMethod("getValue");
@@ -67,199 +45,137 @@ public class QueryBuilder {
return ((Enum<?>) fieldValue).name();
}
}
if (fieldValue instanceof LocalDateTime ldt) {
return ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
return fieldValue;
}
/**
* Escapa los caracteres especiales en una cadena para evitar inyecciones SQL.
* @param value the string value to escape
* @return the escaped string
*/
private String formatValue(Object value) {
if (value == null) return "NULL";
if (value instanceof UUID uuid) {
return "UNHEX(REPLACE('" + uuid.toString() + "','-',''))";
}
if (value instanceof String || value instanceof LocalDateTime) {
return "'" + escapeSql(value.toString()) + "'";
}
return value.toString();
}
private static String escapeSql(String value) {
return value.replace("'", "''");
}
/**
* Construye una consulta SELECT para la clase dada, con columnas opcionales.
* @param clazz the entity class to query
* @param columns optional columns to select; if empty, selects all columns
* @return the current QueryBuilder instance
* @param <T> the type of the entity class
* @param clazz La clase de la entidad a seleccionar
* @param columns Columnas a seleccionar, si no se pasan selecciona "*"
*/
public static <T> QueryBuilder select(Class<T> clazz, String... columns) {
if (clazz == null) {
throw new IllegalArgumentException("Class cannot be null");
}
QueryBuilder qb = new QueryBuilder();
qb.entityClass = clazz;
String tableName = getTableName(clazz);
qb.query.append("SELECT ");
if (columns.length == 0) {
qb.query.append("* ");
} else {
StringJoiner joiner = new StringJoiner(", ");
for (String column : columns) {
if (column != null) {
joiner.add(column);
}
}
qb.query.append(joiner).append(" ");
}
qb.query.append("FROM ").append(tableName).append(" ");
if (columns.length == 0) qb.query.append("* ");
else qb.query.append(String.join(", ", columns)).append(" ");
qb.query.append("FROM ").append(getTableName(clazz)).append(" ");
return qb;
}
/**
* Añade una cláusula WHERE a la consulta actual, filtrando por los campos del mapa.
* Los valores pueden ser números o cadenas, y se manejan adecuadamente.
*
* @param filters un mapa de filtros donde la clave es el nombre del campo y el valor es el valor a filtrar
* @return el QueryBuilder actual para encadenar más métodos
* @param filters Mapa clave = valor para el WHERE. Detecta UUID y Strings tipo IN
*/
public QueryBuilder where(Map<String, String> filters) {
if (filters == null || filters.isEmpty()) {
public QueryBuilder where(Map<String, Object> filters) {
if (filters == null || filters.isEmpty() || entityClass == null) return this;
Map<String, String> javaToSql = new HashMap<>();
Map<String, String> sqlToSql = new HashMap<>();
for (Field f : entityClass.getDeclaredFields()) {
String sqlCol = getColumnName(f);
javaToSql.put(f.getName(), sqlCol);
sqlToSql.put(sqlCol, sqlCol);
}
List<String> conditions = new ArrayList<>();
for (Map.Entry<String, Object> entry : filters.entrySet()) {
String key = entry.getKey();
String sqlCol = javaToSql.getOrDefault(key, sqlToSql.get(key));
if (sqlCol == null) {
LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
continue;
}
Object val = entry.getValue();
if (val == null) continue;
if (val instanceof String s && s.startsWith("(") && s.endsWith(")")) conditions.add(sqlCol + " IN " + s);
else conditions.add(sqlCol + " = " + formatValue(val));
}
if (!conditions.isEmpty()) {
String prefix = query.toString().contains("WHERE") ? "AND " : "WHERE ";
query.append(prefix).append(String.join(" AND ", conditions)).append(" ");
}
return this;
}
Set<String> validFields = entityClass != null
? Arrays.stream(entityClass.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet())
: Collections.emptySet();
/**
* @param object Objeto con campos para WHERE. Soporta UUID y LocalDateTime
*/
public <T> QueryBuilder where(T object) {
if (object == null) throw new IllegalArgumentException("Object cannot be null");
if (entityClass == null) return this;
Set<String> validColumns = Arrays.stream(entityClass.getDeclaredFields())
.map(QueryBuilder::getColumnName)
.collect(Collectors.toSet());
List<String> conditions = new ArrayList<>();
for (Map.Entry<String, String> entry : filters.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (!validFields.contains(key)) {
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
continue;
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
Object value = field.get(object);
if (value != null) {
String col = getColumnName(field);
if (!validColumns.contains(col)) continue;
Object extracted = extractValue(value);
conditions.add(col + " = " + formatValue(extracted));
}
if (value.startsWith("(") && value.endsWith(")")) {
conditions.add(key + " IN " + value);
} else if (value.matches("-?\\d+(\\.\\d+)?")) {
conditions.add(key + " = " + value);
} else {
conditions.add(key + " = '" + value + "'");
} catch (IllegalAccessException e) {
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
if (!conditions.isEmpty()) {
query.append("WHERE ").append(String.join(" AND ", conditions)).append(" ");
String prefix = query.toString().contains("WHERE") ? "AND " : "WHERE ";
query.append(prefix).append(String.join(" AND ", conditions)).append(" ");
}
return this;
}
/**
* Añade una cláusula WHERE a la consulta actual, filtrando por los campos del objeto.
* Los valores se extraen mediante reflexión y se manejan adecuadamente.
*
* @param object el objeto del cual se extraerán los campos para filtrar
* @return el QueryBuilder actual para encadenar más métodos
*/
public <T> QueryBuilder where(T object) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
Set<String> validFields = entityClass != null
? Arrays.stream(entityClass.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet())
: Collections.emptySet();
this.query.append("WHERE ");
StringJoiner joiner = new StringJoiner(" AND ");
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
Object fieldValue = field.get(object);
if (fieldValue != null) {
String key = field.getName();
if (!validFields.contains(key)) {
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
continue;
}
Object value = extractValue(fieldValue);
if (value instanceof String || value instanceof LocalDateTime) {
joiner.add(key + " = '" + value + "'");
} else {
joiner.add(key + " = " + value.toString());
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
this.query.append(joiner).append(" ");
return this;
}
/**
* Construye una consulta INSERT para el objeto dado, insertando todos sus campos.
* Los valores se extraen mediante reflexión y se manejan adecuadamente.
*
* @param object el objeto a insertar
* @return el QueryBuilder actual para encadenar más métodos
* @param <T> el tipo del objeto a insertar
*/
public static <T> QueryBuilder insert(T object) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
if (object == null) throw new IllegalArgumentException("Object cannot be null");
QueryBuilder qb = new QueryBuilder();
String table = getTableName(object.getClass());
qb.query.append("INSERT INTO ").append(table).append(" ");
qb.query.append("(");
StringJoiner columns = new StringJoiner(", ");
StringJoiner values = new StringJoiner(", ");
StringJoiner cols = new StringJoiner(", ");
StringJoiner vals = new StringJoiner(", ");
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
columns.add(field.getName());
Object fieldValue = field.get(object);
if (fieldValue != null) {
Object value = extractValue(fieldValue);
if (value instanceof String || value instanceof LocalDateTime) {
values.add("'" + escapeSql((String) value) + "'");
} else {
values.add(value.toString());
}
} else {
values.add("NULL");
}
} catch (IllegalArgumentException | IllegalAccessException e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
cols.add(getColumnName(field));
Object value = extractValue(field.get(object));
vals.add(qb.formatValue(value));
} catch (IllegalAccessException e) {
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
qb.query.append(columns).append(") ");
qb.query.append("VALUES (").append(values).append(") RETURNING * ");
qb.query.append("(").append(cols).append(") VALUES (").append(vals).append(") RETURNING * ");
return qb;
}
/**
* Construye una consulta UPDATE para el objeto dado, actualizando todos sus campos.
* Los valores se extraen mediante reflexión y se manejan adecuadamente.
* Requiere que el objeto tenga un campo ID (terminado en _id) para la cláusula WHERE.
*
* @param object el objeto a actualizar
* @return el QueryBuilder actual para encadenar más métodos
* @param <T> el tipo del objeto a actualizar
*/
public static <T> QueryBuilder update(T object) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
private static <T> QueryBuilder buildUpdate(T object, boolean includeNulls) {
if (object == null) throw new IllegalArgumentException("Object cannot be null");
QueryBuilder qb = new QueryBuilder();
String table = getTableName(object.getClass());
@@ -267,103 +183,36 @@ public class QueryBuilder {
StringJoiner setJoiner = new StringJoiner(", ");
StringJoiner whereJoiner = new StringJoiner(" AND ");
Field idField = null;
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
Object fieldValue = field.get(object);
if (fieldValue == null) continue;
String fieldName = field.getName();
Object value = extractValue(fieldValue);
if (fieldName.endsWith("_id")) {
String col = getColumnName(field);
Object value = extractValue(field.get(object));
if (col.endsWith("_id")) {
idField = field;
whereJoiner.add(fieldName + " = " + (value instanceof String
|| value instanceof LocalDateTime ? "'" + value + "'" : value));
if (value != null) whereJoiner.add(col + " = " + qb.formatValue(value));
else throw new IllegalArgumentException("ID field cannot be null");
continue;
}
setJoiner.add(fieldName + " = " + (value instanceof String
|| value instanceof LocalDateTime ? "'" + value + "'" : value));
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
if (value != null) setJoiner.add(col + " = " + qb.formatValue(value));
else if (includeNulls) setJoiner.add(col + " = NULL");
} catch (IllegalAccessException e) {
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
if (idField == null) {
throw new IllegalArgumentException("No ID field (ending with _id) found for WHERE clause");
}
if (idField == null) throw new IllegalArgumentException("No ID field (ending with _id) found for WHERE clause");
qb.query.append(setJoiner).append(" WHERE ").append(whereJoiner);
qb.query.append(setJoiner).append(" WHERE ").append(whereJoiner).append(" ");
return qb;
}
/**
* Construye una consulta UPDATE que establece los campos a NULL si son nulos.
* Requiere que el objeto tenga un campo ID (terminado en _id) para la cláusula WHERE.
*
* @param object el objeto a actualizar
* @return el QueryBuilder actual para encadenar más métodos
* @param <T> el tipo del objeto a actualizar
*/
public static <T> QueryBuilder updateWithNulls(T object) {
if (object == null) {
throw new IllegalArgumentException("Object cannot be null");
}
public static <T> QueryBuilder update(T object) { return buildUpdate(object, false); }
QueryBuilder qb = new QueryBuilder();
String table = getTableName(object.getClass());
qb.query.append("UPDATE ").append(table).append(" SET ");
public static <T> QueryBuilder updateWithNulls(T object) { return buildUpdate(object, true); }
StringJoiner setJoiner = new StringJoiner(", ");
StringJoiner whereJoiner = new StringJoiner(" AND ");
Field idField = null;
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
String fieldName = field.getName();
Object fieldValue = field.get(object);
if (fieldName.endsWith("_id")) {
idField = field;
Object value = extractValue(fieldValue);
whereJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value));
continue;
}
if (fieldValue == null) {
setJoiner.add(fieldName + " = NULL");
} else {
Object value = extractValue(fieldValue);
setJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value));
}
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
if (idField == null) {
throw new IllegalArgumentException("No ID field (ending with _id) found for WHERE clause");
}
qb.query.append(setJoiner).append(" WHERE ").append(whereJoiner);
return qb;
}
/**
* Construye una consulta UPSERT (INSERT o UPDATE) para el objeto dado.
* Si hay claves de conflicto, se actualizan los campos excepto las claves duplicadas.
*
* @param object el objeto a insertar o actualizar
* @param conflictKeys las claves que causan conflictos y no deben actualizarse
* @return el QueryBuilder actual para encadenar más métodos
* @param <T> el tipo del objeto a insertar o actualizar
*/
public static <T> QueryBuilder upsert(T object, String... conflictKeys) {
if (object == null) throw new IllegalArgumentException("Object cannot be null");
@@ -371,146 +220,75 @@ public class QueryBuilder {
String table = getTableName(object.getClass());
qb.query.append("INSERT INTO ").append(table).append(" ");
StringJoiner columns = new StringJoiner(", ");
StringJoiner values = new StringJoiner(", ");
StringJoiner cols = new StringJoiner(", ");
StringJoiner vals = new StringJoiner(", ");
Map<String, String> updates = new HashMap<>();
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
Object fieldValue = field.get(object);
String columnName = field.getName();
columns.add(columnName);
Object value = extractValue(fieldValue);
String valueStr = value == null ? "NULL"
: (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value.toString());
values.add(valueStr);
// no actualizamos la clave duplicada
boolean isConflictKey = Arrays.asList(conflictKeys).contains(columnName);
if (!isConflictKey) {
updates.put(columnName, valueStr);
}
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
String col = getColumnName(field);
Object value = extractValue(field.get(object));
String formatted = qb.formatValue(value);
cols.add(col);
vals.add(formatted);
if (!Arrays.asList(conflictKeys).contains(col)) updates.put(col, formatted);
} catch (IllegalAccessException e) {
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
qb.query.append("(").append(columns).append(") VALUES (").append(values).append(")");
if (conflictKeys.length > 0 && !updates.isEmpty()) {
qb.query.append("(").append(cols).append(") VALUES (").append(vals).append(")");
if (!updates.isEmpty() && conflictKeys.length > 0) {
qb.query.append(" ON DUPLICATE KEY UPDATE ");
StringJoiner updateSet = new StringJoiner(", ");
updates.forEach((k, v) -> updateSet.add(k + " = " + v));
qb.query.append(updateSet);
qb.query.append(updates.entrySet().stream()
.map(e -> e.getKey() + " = " + e.getValue())
.collect(Collectors.joining(", ")));
}
return qb;
}
/**
* Construye una consulta DELETE para el objeto dado, eliminando registros que coincidan con sus campos.
* Los valores se extraen mediante reflexión y se manejan adecuadamente.
*
* @param object el objeto a eliminar
* @return el QueryBuilder actual para encadenar más métodos
* @param <T> el tipo del objeto a eliminar
*/
public static <T> QueryBuilder delete(T object) {
if (object == null) throw new IllegalArgumentException("Object cannot be null");
QueryBuilder qb = new QueryBuilder();
String table = getTableName(object.getClass());
qb.query.append("DELETE FROM ").append(table).append(" WHERE ");
StringJoiner joiner = new StringJoiner(" AND ");
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
Object fieldValue = field.get(object);
if (fieldValue != null) {
Object value = extractValue(fieldValue);
joiner.add(field.getName() + " = " + (value instanceof String
|| value instanceof LocalDateTime ? "'" + value + "'" : value.toString()));
}
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
qb.entityClass = object.getClass();
String table = getTableName(qb.entityClass);
qb.query.append("DELETE FROM ").append(table).append(" ");
return qb.where(object);
}
qb.query.append(joiner).append(" ");
return qb;
}
/**
* Añade una cláusula ORDER BY a la consulta actual, ordenando por la columna y el orden especificados.
* Si la columna no es válida, se ignora.
*
* @param column la columna por la que ordenar
* @param order el orden (ASC o DESC); si no se especifica, se asume ASC
* @return el QueryBuilder actual para encadenar más métodos
*/
public QueryBuilder orderBy(Optional<String> column, Optional<String> order) {
column.ifPresent(c -> {
if (entityClass != null) {
boolean isValid = Arrays.stream(entityClass.getDeclaredFields())
.map(Field::getName)
boolean valid = Arrays.stream(entityClass.getDeclaredFields())
.map(QueryBuilder::getColumnName)
.anyMatch(f -> f.equals(c));
if (!isValid) {
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en ORDER BY: {}", c);
if (!valid) {
LOGGER.warn("[QueryBuilder] Ignorando campo invalido en ORDER BY: {}", c);
return;
}
}
sort = "ORDER BY " + c + " ";
order.ifPresent(o -> sort += o.equalsIgnoreCase("asc") ? "ASC" : "DESC" + " ");
orderByClause = "ORDER BY " + c + " " + order.orElse("ASC") + " ";
});
return this;
}
/**
* Añade una cláusula LIMIT a la consulta actual, limitando el número de resultados.
* Si se especifica un offset, se añade también.
*
* @param limitParam el número máximo de resultados a devolver; si no se especifica, no se aplica límite
* @return el QueryBuilder actual para encadenar más métodos
*/
public QueryBuilder limit(Optional<Integer> limitParam) {
limitParam.ifPresent(param -> limit = "LIMIT " + param + " ");
limitParam.ifPresent(l -> limitClause = "LIMIT " + l + " ");
return this;
}
/**
* Añade una cláusula OFFSET a la consulta actual, desplazando el inicio de los resultados.
* Si se especifica un offset, se añade también.
*
* @param offsetParam el número de resultados a omitir antes de empezar a devolver resultados; si no se especifica, no se aplica offset
* @return el QueryBuilder actual para encadenar más métodos
*/
public QueryBuilder offset(Optional<Integer> offsetParam) {
offsetParam.ifPresent(param -> limit += "OFFSET " + param + " ");
offsetParam.ifPresent(o -> offsetClause = "OFFSET " + o + " ");
return this;
}
/**
* Construye y devuelve la consulta SQL completa.
* Si no se han añadido cláusulas ORDER BY, LIMIT o OFFSET, las omite.
*
* @return la consulta SQL construida
*/
public String build() {
if (order != null && !order.isEmpty()) {
query.append(order);
}
if (sort != null && !sort.isEmpty()) {
query.append(sort);
}
if (limit != null && !limit.isEmpty()) {
query.append(limit);
}
if (orderByClause != null) query.append(orderByClause);
if (limitClause != null) query.append(limitClause);
if (offsetClause != null) query.append(offsetClause);
return query.toString().trim() + ";";
}
}

View File

@@ -0,0 +1,27 @@
package net.miarma.api.backlib.gson;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.vertx.core.json.JsonObject;
import net.miarma.api.backlib.interfaces.IValuableEnum;
import java.time.LocalDateTime;
public class GsonProvider {
private static final Gson INSTANCE = createGson();
private static Gson createGson() {
return new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
.registerTypeAdapter(JsonObject.class, new JsonObjectTypeAdapter())
.registerTypeHierarchyAdapter(IValuableEnum.class, new ValuableEnumDeserializer())
.registerTypeHierarchyAdapter(IValuableEnum.class, new ValuableEnumTypeAdapter())
.addSerializationExclusionStrategy(new APIDontReturnExclusionStrategy())
.create();
}
public static Gson get() {
return INSTANCE;
}
private GsonProvider() {}
}

View File

@@ -4,7 +4,8 @@ import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import net.miarma.api.backlib.ValuableEnum;
import net.miarma.api.backlib.interfaces.IValuableEnum;
import java.lang.reflect.Type;
import java.util.Arrays;
@@ -15,15 +16,15 @@ import java.util.Arrays;
*
* @author José Manuel Amador Gallardo
*/
public class ValuableEnumDeserializer implements JsonDeserializer<ValuableEnum> {
public class ValuableEnumDeserializer implements JsonDeserializer<IValuableEnum> {
@Override
public ValuableEnum deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
public IValuableEnum deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Class<?> enumClass = (Class<?>) typeOfT;
int value = json.getAsInt();
return (ValuableEnum) Arrays.stream(enumClass.getEnumConstants())
.filter(e -> ((ValuableEnum) e).getValue() == value)
return (IValuableEnum) Arrays.stream(enumClass.getEnumConstants())
.filter(e -> ((IValuableEnum) e).getValue() == value)
.findFirst()
.orElseThrow(() -> new JsonParseException("Invalid enum value: " + value));
}

View File

@@ -4,7 +4,8 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.miarma.api.backlib.ValuableEnum;
import net.miarma.api.backlib.interfaces.IValuableEnum;
import java.lang.reflect.Type;
@@ -14,10 +15,10 @@ import java.lang.reflect.Type;
*
* @author José Manuel Amador Gallardo
*/
public class ValuableEnumTypeAdapter implements JsonSerializer<ValuableEnum> {
public class ValuableEnumTypeAdapter implements JsonSerializer<IValuableEnum> {
@Override
public JsonElement serialize(ValuableEnum src, Type typeOfSrc, JsonSerializationContext context) {
public JsonElement serialize(IValuableEnum src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getValue());
}
}

View File

@@ -1,21 +0,0 @@
package net.miarma.api.backlib.interfaces;
import java.time.LocalDateTime;
import net.miarma.api.backlib.Constants.CoreUserGlobalStatus;
import net.miarma.api.backlib.Constants.CoreUserRole;
public interface IUser {
Integer getUser_id();
String getUser_name();
String getEmail();
String getDisplay_name();
String getPassword();
String getAvatar();
CoreUserGlobalStatus getGlobal_status();
CoreUserRole getGlobal_role();
LocalDateTime getCreated_at();
default LocalDateTime getUpdated_at() {
return null;
}
}

View File

@@ -1,7 +1,5 @@
package net.miarma.api.backlib.interfaces;
import net.miarma.api.backlib.ValuableEnum;
public interface IUserRole extends ValuableEnum {
public interface IUserRole extends IValuableEnum {
String name();
}

View File

@@ -1,9 +1,9 @@
package net.miarma.api.backlib;
package net.miarma.api.backlib.interfaces;
/**
* Interfaz que define un enum con un valor entero asociado
* @author José Manuel Amador Gallardo
*/
public interface ValuableEnum {
public interface IValuableEnum {
int getValue();
}

View File

@@ -1,4 +1,4 @@
package net.miarma.api.backlib;
package net.miarma.api.backlib.log;
import org.slf4j.Logger;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.backlib;
package net.miarma.api.backlib.log;
public record LogEntry(int order, String message) {
public static LogEntry of(int order, String message) {

View File

@@ -0,0 +1,11 @@
package net.miarma.api.backlib.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerProvider {
public static Logger getLogger() {
String className = Thread.currentThread().getStackTrace()[2].getClassName();
return LoggerFactory.getLogger(className);
}
}

View File

@@ -1,5 +1,6 @@
package net.miarma.api.backlib.middlewares;
import java.util.UUID;
import java.util.function.Consumer;
import io.vertx.core.Handler;
@@ -18,7 +19,7 @@ import net.miarma.api.backlib.util.JsonUtil;
public abstract class AbstractAuthGuard<U, R extends Enum<R> & IUserRole> {
protected abstract R parseRole(String roleStr);
protected abstract void getUserEntity(int userId, RoutingContext ctx, Consumer<U> callback);
protected abstract void getUserEntity(UUID userId, RoutingContext ctx, Consumer<U> callback);
protected abstract boolean hasPermission(U user, R role);
public Handler<RoutingContext> check(R... allowedRoles) {
@@ -29,7 +30,7 @@ public abstract class AbstractAuthGuard<U, R extends Enum<R> & IUserRole> {
return;
}
int userId = JWTManager.getInstance().extractUserId(token);
UUID userId = JWTManager.getInstance().extractUserId(token);
String roleStr = JWTManager.getInstance().extractRole(token);
R role;

View File

@@ -1,24 +1,14 @@
package net.miarma.api.backlib.security;
import java.util.Date;
import java.util.UUID;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.interfaces.IUserRole;
/**
* Clase de gestión de JSON Web Tokens (JWT).
* Proporciona métodos para generar, verificar y decodificar tokens JWT.
* <p>
* Esta clase sigue el patron Singleton para asegurar una sola instancia.
*
* @author José Manuel Amador Gallardo
*/
public class JWTManager {
private final ConfigManager config = ConfigManager.getInstance();
@@ -31,11 +21,6 @@ public class JWTManager {
this.verifier = JWT.require(algorithm).build();
}
/**
* Obtiene la instancia única de JWTManager.
*
* @return La instancia única de JWTManager.
*/
public static synchronized JWTManager getInstance() {
if (instance == null) {
instance = new JWTManager();
@@ -44,113 +29,54 @@ public class JWTManager {
}
/**
* Genera un token JWT para un usuario.
*
* @param user El usuario para el cual se generará el token.
* @param keepLoggedIn Indica si el token debe tener una duración prolongada.
* @return El token JWT generado.
* Genera un token JWT usando UUID para el usuario.
*/
public String generateToken(String user_name, Integer user_id, IUserRole role, boolean keepLoggedIn) {
final long EXPIRATION_TIME_MS = 1000L * (keepLoggedIn ? config.getIntProperty("jwt.expiration") : config.getIntProperty("jwt.expiration.short"));
public String generateToken(
String displayName,
UUID userId,
IUserRole role,
Integer serviceId,
boolean keepLoggedIn
) {
final long expiration = 1000L * (
keepLoggedIn
? config.getIntProperty("jwt.expiration")
: config.getIntProperty("jwt.expiration.short")
);
return JWT.create()
.withSubject(user_name)
.withClaim("userId", user_id)
.withSubject(displayName)
.withClaim("userId", userId.toString())
.withClaim("serviceId", serviceId)
.withClaim("role", role.name())
.withClaim("isAdmin", role == Constants.CoreUserRole.ADMIN)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME_MS))
.withExpiresAt(new Date(System.currentTimeMillis() + expiration))
.sign(algorithm);
}
/**
* Decodifica un token JWT sin verificar su firma.
*
* @param token El token JWT a decodificar.
* @return Un objeto DecodedJWT que contiene la información del token.
*/
public DecodedJWT decodeWithoutVerification(String token) {
public DecodedJWT decode(String token) {
return JWT.decode(token);
}
/**
* Verifica la validez de un token JWT.
*
* @param token El token JWT a verificar.
* @return true si el token es válido, false en caso contrario.
*/
public boolean isValid(String token) {
try {
verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Verifica si un token JWT pertenece a un usuario administrador.
*
* @param token El token JWT a verificar.
* @return true si el token pertenece a un administrador, false en caso contrario.
*/
public boolean isAdmin(String token) {
public UUID extractUserId(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("isAdmin").asBoolean();
} catch (Exception e) {
return false;
}
}
/**
* Obtiene el ID de usuario a partir de un token JWT.
*
* @param token El token JWT del cual se extraerá el ID de usuario.
* @return El ID de usuario si el token es válido, -1 en caso contrario.
*/
public int getUserId(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("userId").asInt();
} catch (Exception e) {
return -1;
}
}
/**
* Obtiene el sub especificado en un token JWT, que generalmente es el nombre de usuario.
*
* @param token El token JWT del cual se extraerá el nombre de usuario.
* @return El nombre de usuario si el token es válido, null en caso contrario.
*/
public String getSubject(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getSubject();
String uuidStr = jwt.getClaim("userId").asString();
return UUID.fromString(uuidStr);
} catch (Exception e) {
return null;
}
}
/**
* Extrae el ID de usuario de un token JWT.
* @param token El token JWT del cual se extraerá el ID de usuario.
* @return El ID de usuario si el token es válido, -1 en caso contrario.
*/
public int extractUserId(String token) {
public Integer extractServiceId(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("userId").asInt();
return jwt.getClaim("serviceId").asInt();
} catch (Exception e) {
return -1;
}
}
/**
* Extrae el rol de usuario de un token JWT.
* @param token El token JWT del cual se extraerá el ID de usuario.
* @return El rol de usuario si el token es válido, null en caso contrario.
*/
public String extractRole(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
@@ -159,4 +85,13 @@ public class JWTManager {
return null;
}
}
public boolean isValid(String token) {
try {
verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
}

View File

@@ -1,7 +1,5 @@
package net.miarma.api.backlib.security;
import net.miarma.api.backlib.ConfigManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -10,6 +8,8 @@ import java.security.SecureRandom;
import java.util.List;
import java.util.Properties;
import net.miarma.api.backlib.config.ConfigManager;
/**
* Clase encargada de generar los secrets necesarios para la autenticación JWT.
* Si el secret ya existe en el archivo de configuración, lo devuelve.

View File

@@ -0,0 +1,16 @@
package net.miarma.api.backlib.util;
import java.util.UUID;
import io.vertx.core.buffer.Buffer;
public class BufferUtil {
public static UUID uuidFromBuffer(Buffer b) {
if (b == null || b.length() != 16) {
return null;
}
long mostSigBits = b.getLong(0);
long leastSigBits = b.getLong(8);
return new UUID(mostSigBits, leastSigBits);
}
}

View File

@@ -3,7 +3,7 @@ package net.miarma.api.backlib.util;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.ApiResponse;
import net.miarma.api.backlib.http.ApiStatus;
@@ -26,7 +26,7 @@ public class JsonUtil {
.put("data", data);
ctx.response().end(response.encode());
} else {
ctx.response().end(Constants.GSON.toJson(new ApiResponse<>(status, message, data)));
ctx.response().end(GsonProvider.get().toJson(new ApiResponse<>(status, message, data)));
}
}

View File

@@ -1,13 +1,16 @@
package net.miarma.api.backlib.util;
import org.slf4j.Logger;
import io.vertx.ext.web.Router;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.log.LoggerProvider;
/**
* Clase de utilidad para adjuntar un logger a un router de Vert.x.
* @author José Manuel Amador Gallardo
*/
public class RouterUtil {
private final static Logger LOGGER = LoggerProvider.getLogger();
public static void attachLogger(Router router) {
router.route().handler(ctx -> {
@@ -46,7 +49,7 @@ public class RouterUtil {
duration
);
Constants.LOGGER.info(log);
LOGGER.info(log);
});
ctx.next();

View File

@@ -5,8 +5,8 @@
<parent>
<groupId>net.miarma.api</groupId>
<artifactId>miarma-ecosystem</artifactId>
<version>1.2.1</version>
<artifactId>miarma-backend</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>bootstrap</artifactId>
@@ -39,6 +39,7 @@
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<!--
<dependency>
<groupId>net.miarma.api</groupId>
<artifactId>huertos</artifactId>
@@ -51,7 +52,7 @@
</dependency>
<dependency>
<groupId>net.miarma.api</groupId>
<artifactId>miarmacraft</artifactId>
<artifactId>minecraft</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
@@ -59,6 +60,7 @@
<artifactId>mpaste</artifactId>
<version>${project.version}</version>
</dependency>
-->
</dependencies>
<build>

View File

@@ -1,18 +1,19 @@
package net.miarma.api;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.security.SecretManager;
import net.miarma.api.backlib.vertx.VertxJacksonConfig;
import net.miarma.api.backlib.util.MessageUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import org.slf4j.Logger;
import io.vertx.core.Launcher;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.backlib.security.SecretManager;
import net.miarma.api.backlib.util.MessageUtil;
import net.miarma.api.backlib.vertx.VertxJacksonConfig;
/**
* Punto de entrada para inicializar la aplicación.
@@ -24,12 +25,13 @@ import io.vertx.core.Launcher;
* - Desplegar el Verticle Master
*/
public class AppInitializer {
private final static Logger LOGGER = LoggerProvider.getLogger();
public static void main(String[] args) {
AppInitializer initializer = new AppInitializer();
initializer.init();
initializer.deployMaster();
Constants.LOGGER.info("✅ App initialized successfully!");
LOGGER.info("✅ App initialized successfully!");
}
private final ConfigManager configManager;
@@ -49,7 +51,7 @@ public class AppInitializer {
private void initializeDirectories() {
File baseDir = new File(configManager.getBaseDir());
if (!baseDir.exists() && baseDir.mkdirs()) {
Constants.LOGGER.info("Created base directory: " + baseDir.getAbsolutePath());
LOGGER.info("Created base directory: " + baseDir.getAbsolutePath());
}
}
@@ -59,12 +61,12 @@ public class AppInitializer {
try (InputStream in = getClass().getClassLoader().getResourceAsStream("default.properties")) {
if (in != null) {
Files.copy(in, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
Constants.LOGGER.info("Copied default.properties to: " + configFile.getAbsolutePath());
LOGGER.info("Copied default.properties to: " + configFile.getAbsolutePath());
} else {
Constants.LOGGER.error(MessageUtil.notFound("Default config", "resources"));
LOGGER.error(MessageUtil.notFound("Default config", "resources"));
}
} catch (IOException e) {
Constants.LOGGER.error(MessageUtil.failedTo("copy", "default config", e));
LOGGER.error(MessageUtil.failedTo("copy", "default config", e));
}
}
}

View File

@@ -1,32 +1,38 @@
package net.miarma.api;
import java.util.Set;
import java.util.List;
import org.slf4j.Logger;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.LogAccumulator;
import net.miarma.api.backlib.log.LogAccumulator;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.backlib.util.DeploymentUtil;
import net.miarma.api.microservices.core.verticles.CoreMainVerticle;
import net.miarma.api.microservices.huertos.verticles.HuertosMainVerticle;
import net.miarma.api.microservices.huertosdecine.verticles.CineMainVerticle;
import net.miarma.api.microservices.miarmacraft.verticles.MMCMainVerticle;
import net.miarma.api.microservices.mpaste.verticles.MPasteMainVerticle;
//import net.miarma.api.microservices.huertos.verticles.HuertosMainVerticle;
//import net.miarma.api.microservices.huertosdecine.verticles.CineMainVerticle;
//import net.miarma.api.microservices.minecraft.verticles.MMCMainVerticle;
//import net.miarma.api.microservices.mpaste.verticles.MPasteMainVerticle;
@SuppressWarnings("unused")
public class MasterVerticle extends AbstractVerticle {
private final Logger LOGGER = LoggerProvider.getLogger();
@Override
public void start(Promise<Void> startPromise) {
deploy()
.onSuccess(v -> {
vertx.setTimer(300, id -> {
LogAccumulator.flushToLogger(Constants.LOGGER);
LogAccumulator.flushToLogger(LOGGER);
startPromise.complete();
});
})
.onFailure(startPromise::fail);
}
@SuppressWarnings("unused")
private Future<Void> deploy() {
Promise<Void> promise = Promise.promise();
@@ -34,7 +40,7 @@ public class MasterVerticle extends AbstractVerticle {
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(CoreMainVerticle.class)))
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(CoreMainVerticle.class, err)));
Future<String> huertos = vertx.deployVerticle(new HuertosMainVerticle())
/*Future<String> huertos = vertx.deployVerticle(new HuertosMainVerticle())
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(HuertosMainVerticle.class)))
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(HuertosMainVerticle.class, err)));
@@ -48,9 +54,10 @@ public class MasterVerticle extends AbstractVerticle {
Future<String> mpaste = vertx.deployVerticle(new MPasteMainVerticle())
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(MPasteMainVerticle.class)))
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(MPasteMainVerticle.class, err)));
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(MPasteMainVerticle.class, err)));*/
Future.all(core, huertos, mmc, cine, mpaste)
//Future.all(core, huertos, mmc, cine, mpaste)
Future.all(List.of(core))
.onSuccess(_ -> promise.complete())
.onFailure(promise::fail);

View File

@@ -1,3 +1,25 @@
# App Configuration
app.name=miarma-backend
app.version=v2
# API Configuration
api.base=/api
api.core.prefix=/core/${app.version}
api.auth.prefix=/auth/${app.version}
api.huertos.prefix=/huertos/${app.version}
api.minecraft.prefix=/minecraft/${app.version}
api.cine.prefix=/cine/${app.version}
api.mpaste.prefix=/mpaste/${app.version}
# EventBus Configuration
eventbus.auth.address=auth.eventbus
eventbus.core.address=core.eventbus
eventbus.huertos.address=huertos.eventbus
eventbus.minecraft.address=minecraft.eventbus
eventbus.cine.address=cine.eventbus
eventbus.mpaste.address=mpaste.eventbus
# DB Configuration
db.protocol=jdbc:mariadb
db.host=localhost
@@ -9,10 +31,10 @@ dp.poolSize=5
# HTTP Server Configuration
inet.host=localhost
sso.logic.port=8080
sso.data.port=8081
mmc.logic.port=8100
mmc.data.port=8101
core.logic.port=8080
core.data.port=8081
minecraft.logic.port=8100
minecraft.data.port=8101
huertos.logic.port=8120
huertos.data.port=8121
cine.data.port = 8140

View File

@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma.api</groupId>
<artifactId>core</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
@@ -20,7 +20,7 @@
<dependency>
<groupId>net.miarma.api</groupId>
<artifactId>backlib</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,172 @@
package net.miarma.api.microservices.core.dao;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.sqlclient.Pool;
import net.miarma.api.microservices.core.entities.CredentialEntity;
import net.miarma.api.backlib.db.DataAccessObject;
import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
public class CredentialDAO implements DataAccessObject<CredentialEntity, UUID> {
private final DatabaseManager db;
public CredentialDAO(Pool pool) {
this.db = DatabaseManager.getInstance(pool);
}
@Override
public Future<List<CredentialEntity>> getAll() {
return getAll(new QueryParams(Map.of(), new QueryFilters()));
}
@Override
public Future<CredentialEntity> getById(UUID id) {
Promise<CredentialEntity> promise = Promise.promise();
String query = QueryBuilder
.select(CredentialEntity.class)
.where(Map.of("credential_id", id.toString()))
.build();
db.executeOne(query, CredentialEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
public Future<List<CredentialEntity>> getAll(QueryParams params) {
Promise<List<CredentialEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(CredentialEntity.class)
.where(params.getFilters())
.orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder())
.limit(params.getQueryFilters().getLimit())
.offset(params.getQueryFilters().getOffset())
.build();
db.execute(query, CredentialEntity.class,
list -> promise.complete(list.isEmpty() ? List.of() : list),
promise::fail
);
return promise.future();
}
/**
* Útil para el login: busca la credencial por servicio y usuario
*/
public Future<CredentialEntity> getByServiceAndUsername(int serviceId, String username) {
Promise<CredentialEntity> promise = Promise.promise();
String query = QueryBuilder
.select(CredentialEntity.class)
.where(Map.of("service_id", String.valueOf(serviceId), "username", username))
.build();
db.executeOne(query, CredentialEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
public Future<List<CredentialEntity>> getByUserId(UUID userId) {
Promise<List<CredentialEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(CredentialEntity.class)
.where(Map.of("user_id", userId.toString()))
.build();
db.execute(query, CredentialEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<CredentialEntity> insert(CredentialEntity credential) {
Promise<CredentialEntity> promise = Promise.promise();
// Si no tiene ID, se lo generamos antes de insertar
if (credential.getCredentialId() == null) {
credential.setCredentialId(UUID.randomUUID());
}
String query = QueryBuilder.insert(credential).build();
db.executeOne(query, CredentialEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<CredentialEntity> upsert(CredentialEntity credential, String... conflictKeys) {
Promise<CredentialEntity> promise = Promise.promise();
String query = QueryBuilder.upsert(credential, conflictKeys).build();
db.executeOne(query, CredentialEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<CredentialEntity> update(CredentialEntity credential) {
Promise<CredentialEntity> promise = Promise.promise();
String query = QueryBuilder.update(credential).build();
db.executeOne(query, CredentialEntity.class,
_ -> promise.complete(credential),
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> delete(UUID id) {
Promise<Boolean> promise = Promise.promise();
CredentialEntity cred = new CredentialEntity();
cred.setCredentialId(id);
String query = QueryBuilder.delete(cred).build();
db.executeOne(query, CredentialEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> exists(UUID id) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(CredentialEntity.class)
.where(Map.of("credential_id", id.toString()))
.build();
db.executeOne(query, CredentialEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
}

View File

@@ -1,4 +1,8 @@
package net.miarma.api.backlib.core.dao;
package net.miarma.api.microservices.core.dao;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.core.Promise;
@@ -8,12 +12,9 @@ import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.core.entities.FileEntity;
import net.miarma.api.microservices.core.entities.FileEntity;
import java.util.List;
import java.util.Map;
public class FileDAO implements DataAccessObject<FileEntity, Integer> {
public class FileDAO implements DataAccessObject<FileEntity, UUID> {
private final DatabaseManager db;
@@ -27,7 +28,7 @@ public class FileDAO implements DataAccessObject<FileEntity, Integer> {
}
@Override
public Future<FileEntity> getById(Integer id) {
public Future<FileEntity> getById(UUID id) {
Promise<FileEntity> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
@@ -60,7 +61,7 @@ public class FileDAO implements DataAccessObject<FileEntity, Integer> {
return promise.future();
}
public Future<List<FileEntity>> getUserFiles(Integer userId) {
public Future<List<FileEntity>> getUserFiles(UUID userId) {
Promise<List<FileEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
@@ -115,7 +116,7 @@ public class FileDAO implements DataAccessObject<FileEntity, Integer> {
}
@Override
public Future<Boolean> exists(Integer id) {
public Future<Boolean> exists(UUID id) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
@@ -131,10 +132,10 @@ public class FileDAO implements DataAccessObject<FileEntity, Integer> {
}
@Override
public Future<Boolean> delete(Integer id) {
public Future<Boolean> delete(UUID id) {
Promise<Boolean> promise = Promise.promise();
FileEntity file = new FileEntity();
file.setFile_id(id);
file.setFileId(id);
String query = QueryBuilder.delete(file).build();

View File

@@ -1,19 +1,20 @@
package net.miarma.api.backlib.core.dao;
package net.miarma.api.microservices.core.dao;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.backlib.db.DataAccessObject;
import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
public class UserDAO implements DataAccessObject<UserEntity, Integer> {
public class UserDAO implements DataAccessObject<UserEntity, UUID> {
private final DatabaseManager db;
@@ -27,13 +28,15 @@ public class UserDAO implements DataAccessObject<UserEntity, Integer> {
}
@Override
public Future<UserEntity> getById(Integer id) {
public Future<UserEntity> getById(UUID id) {
Promise<UserEntity> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(Map.of("user_id", id.toString()))
.where(Map.of("user_id", id))
.build();
System.out.println(query);
db.executeOne(query, UserEntity.class,
promise::complete,
promise::fail
@@ -60,36 +63,6 @@ public class UserDAO implements DataAccessObject<UserEntity, Integer> {
return promise.future();
}
public Future<UserEntity> getByEmail(String email) {
Promise<UserEntity> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(Map.of("email", email))
.build();
db.executeOne(query, UserEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
public Future<UserEntity> getByUserName(String userName) {
Promise<UserEntity> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(Map.of("user_name", userName))
.build();
db.executeOne(query, UserEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<UserEntity> insert(UserEntity user) {
Promise<UserEntity> promise = Promise.promise();
@@ -130,10 +103,10 @@ public class UserDAO implements DataAccessObject<UserEntity, Integer> {
}
@Override
public Future<Boolean> delete(Integer id) {
public Future<Boolean> delete(UUID id) {
Promise<Boolean> promise = Promise.promise();
UserEntity user = new UserEntity();
user.setUser_id(id);
user.setUserId(id);
String query = QueryBuilder.delete(user).build();
@@ -146,7 +119,7 @@ public class UserDAO implements DataAccessObject<UserEntity, Integer> {
}
@Override
public Future<Boolean> exists(Integer id) {
public Future<Boolean> exists(UUID id) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)

View File

@@ -0,0 +1,114 @@
package net.miarma.api.microservices.core.entities;
import java.time.LocalDateTime;
import java.util.UUID;
import com.google.gson.annotations.SerializedName;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.annotations.APIDontReturn;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.db.AbstractEntity;
@Table("credentials")
public class CredentialEntity extends AbstractEntity {
@SerializedName("credential_id")
private UUID credentialId;
@SerializedName("user_id")
private UUID userId;
@SerializedName("service_id")
private int serviceId;
private String username;
private String email;
@APIDontReturn
private String password;
private int status;
@SerializedName("created_at")
private LocalDateTime createdAt;
@SerializedName("updated_at")
private LocalDateTime updatedAt;
public CredentialEntity() { }
public CredentialEntity(Row row) { super(row); }
public UUID getCredentialId() {
return credentialId;
}
public void setCredentialId(UUID credentialId) {
this.credentialId = credentialId;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public int getServiceId() {
return serviceId;
}
public void setServiceId(int serviceId) {
this.serviceId = serviceId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,99 @@
package net.miarma.api.microservices.core.entities;
import java.time.LocalDateTime;
import java.util.UUID;
import com.google.gson.annotations.SerializedName;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.db.AbstractEntity;
import net.miarma.api.microservices.core.enums.CoreFileContext;
@Table("files")
public class FileEntity extends AbstractEntity {
@SerializedName("file_id")
private UUID fileId;
@SerializedName("file_name")
private String fileName;
@SerializedName("file_path")
private String filePath;
@SerializedName("mime_type")
private String mimeType;
@SerializedName("uploaded_by")
private UUID uploadedBy;
private CoreFileContext context;
@SerializedName("uploaded_at")
private LocalDateTime uploadedAt;
public FileEntity() {
super();
}
public FileEntity(Row row) {
super(row);
}
public UUID getFileId() {
return fileId;
}
public void setFileId(UUID fileId) {
this.fileId = fileId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public UUID getUploadedBy() {
return uploadedBy;
}
public void setUploadedBy(UUID uploadedBy) {
this.uploadedBy = uploadedBy;
}
public CoreFileContext getContext() {
return context;
}
public void setContext(CoreFileContext context) {
this.context = context;
}
public LocalDateTime getUploadedAt() {
return uploadedAt;
}
public void setUploadedAt(LocalDateTime uploadedAt) {
this.uploadedAt = uploadedAt;
}
}

View File

@@ -0,0 +1,59 @@
package net.miarma.api.microservices.core.entities;
import java.time.LocalDateTime;
import java.util.UUID;
import com.google.gson.annotations.SerializedName;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.db.AbstractEntity;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.enums.CoreUserGlobalStatus;
@Table("users")
public class UserEntity extends AbstractEntity {
@SerializedName("user_id")
private UUID userId;
@SerializedName("display_name")
private String displayName;
private String avatar;
@SerializedName("global_status")
private CoreUserGlobalStatus globalStatus;
@SerializedName("global_role")
private CoreUserGlobalRole globalRole;
@SerializedName("created_at")
private LocalDateTime createdAt;
@SerializedName("updated_at")
private LocalDateTime updatedAt;
public UserEntity() { }
public UserEntity(Row row) { super(row); }
public UUID getUserId() { return userId; }
public void setUserId(UUID userId) { this.userId = userId; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getAvatar() { return avatar; }
public void setAvatar(String avatar) { this.avatar = avatar; }
public CoreUserGlobalStatus getGlobalStatus() { return globalStatus; }
public void setGlobalStatus(CoreUserGlobalStatus globalStatus) { this.globalStatus = globalStatus; }
public CoreUserGlobalRole getGlobalRole() { return globalRole; }
public void setGlobalRole(CoreUserGlobalRole role) { this.globalRole = role; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

View File

@@ -0,0 +1,37 @@
package net.miarma.api.microservices.core.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum CoreFileContext implements IValuableEnum {
CORE(0),
HUERTOS(1),
MMC(2),
CINE(3);
private final int value;
CoreFileContext(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public String toCtxString() {
return switch(this) {
case CORE -> "core";
case HUERTOS -> "huertos";
case MMC -> "miarmacraft";
case CINE -> "cine";
};
}
public static CoreFileContext fromInt(int i) {
for (CoreFileContext context : values()) {
if (context.value == i) return context;
}
throw new IllegalArgumentException("Invalid CoreFileContext value: " + i);
}
}

View File

@@ -0,0 +1,26 @@
package net.miarma.api.microservices.core.enums;
import net.miarma.api.backlib.interfaces.IUserRole;
public enum CoreUserGlobalRole implements IUserRole {
USER(0),
ADMIN(1);
private final int value;
CoreUserGlobalRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CoreUserGlobalRole fromInt(int i) {
for (CoreUserGlobalRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid CoreUserGlobalRole value: " + i);
}
}

View File

@@ -0,0 +1,26 @@
package net.miarma.api.microservices.core.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum CoreUserGlobalStatus implements IValuableEnum {
INACTIVE(0),
ACTIVE(1);
private final int value;
CoreUserGlobalStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CoreUserGlobalStatus fromInt(int i) {
for (CoreUserGlobalStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid CoreUserGlobalStatus value: " + i);
}
}

View File

@@ -0,0 +1,146 @@
package net.miarma.api.microservices.core.handlers;
import java.util.UUID;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.gson.Gson;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.microservices.core.entities.UserEntity;
@SuppressWarnings("unused")
public class AuthHandler {
private final Gson GSON = GsonProvider.get();
private final Vertx vertx;
private final String AUTH_EVENT_BUS = ConfigManager.getInstance()
.getStringProperty("eventbus.auth.address");
public AuthHandler(Vertx vertx) {
this.vertx = vertx;
}
public void login(RoutingContext ctx) {
vertx.eventBus().request(AUTH_EVENT_BUS, ctx.body().asJsonObject().put("action", "login"))
.onSuccess(reply -> JsonUtil.sendJson(ctx, ApiStatus.OK, reply.body()))
.onFailure(err -> EventBusUtil.handleReplyError(ctx, err));
}
public void loginValidate(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "loginValidate")
.put("userId", body.getInteger("userId"))
.put("password", body.getString("password"));
vertx.eventBus().request(AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void validateToken(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
JsonObject request = new JsonObject()
.put("action", "validateToken")
.put("token", token);
vertx.eventBus().request(AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded() && Boolean.TRUE.equals(ar.result().body())) {
JsonUtil.sendJson(ctx, ApiStatus.OK, true, "Valid token");
} else {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, false, "Invalid token");
}
});
} else {
JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Missing or invalid Authorization header");
}
}
public void refreshToken(RoutingContext ctx) {
String tokenHeader = ctx.request().getHeader("Authorization");
if (tokenHeader == null || !tokenHeader.startsWith("Bearer ")) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Missing or invalid Authorization header");
return;
}
String token = tokenHeader.substring("Bearer ".length());
JWTManager jwt = JWTManager.getInstance();
try {
DecodedJWT decoded = jwt.decode(token);
String userIdStr = decoded.getClaim("userId").asString();
if (userIdStr == null) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token claims");
return;
}
UUID userId = UUID.fromString(userIdStr);
vertx.eventBus().request(AUTH_EVENT_BUS, new JsonObject()
.put("action", "getUserById")
.put("userId", userId.toString()), ar -> {
if (ar.succeeded()) {
JsonObject userJson = (JsonObject) ar.result().body();
UserEntity user = GSON.fromJson(userJson.encode(), UserEntity.class);
String newToken = jwt.generateToken(
user.getDisplayName(),
user.getUserId(),
user.getGlobalRole(),
0,
false
);
JsonUtil.sendJson(ctx, ApiStatus.OK, new JsonObject().put("token", newToken));
} else {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "User not found or service unavailable");
}
});
} catch (Exception e) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token format");
}
}
public void changePassword(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "changePassword")
.put("userId", body.getInteger("userId"))
.put("newPassword", body.getString("newPassword"));
vertx.eventBus().request(AUTH_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, true, "Updated");
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void register(RoutingContext ctx) {
vertx.eventBus().request(AUTH_EVENT_BUS, ctx.body().asJsonObject().put("action", "register"))
.onSuccess(reply -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, null))
.onFailure(err -> EventBusUtil.handleReplyError(ctx, err));
}
}

View File

@@ -1,25 +1,33 @@
package net.miarma.api.backlib.core.handlers;
package net.miarma.api.microservices.core.handlers;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import org.slf4j.Logger;
import com.google.gson.Gson;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.FileUpload;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.Constants.CoreFileContext;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.core.entities.FileEntity;
import net.miarma.api.backlib.core.services.FileService;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.backlib.util.JsonUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import net.miarma.api.microservices.core.entities.FileEntity;
import net.miarma.api.microservices.core.enums.CoreFileContext;
import net.miarma.api.microservices.core.services.FileService;
@SuppressWarnings("unused")
public class FileDataHandler {
private final Logger LOGGER = LoggerProvider.getLogger();
private final Gson GSON = GsonProvider.get();
private final FileService fileService;
public FileDataHandler(Pool pool) {
@@ -35,7 +43,7 @@ public class FileDataHandler {
}
public void getById(RoutingContext ctx) {
Integer fileId = Integer.parseInt(ctx.pathParam("file_id"));
UUID fileId = UUID.fromString(ctx.pathParam("file_id"));
fileService.getById(fileId)
.onSuccess(file -> JsonUtil.sendJson(ctx, ApiStatus.OK, file))
@@ -46,7 +54,7 @@ public class FileDataHandler {
try {
String fileName = ctx.request().getFormAttribute("file_name");
String mimeType = ctx.request().getFormAttribute("mime_type");
int uploadedBy = Integer.parseInt(ctx.request().getFormAttribute("uploaded_by"));
UUID uploadedBy = UUID.fromString(ctx.request().getFormAttribute("uploaded_by"));
int contextValue = Integer.parseInt(ctx.request().getFormAttribute("context"));
FileUpload upload = ctx.fileUploads().stream()
@@ -58,23 +66,23 @@ public class FileDataHandler {
byte[] fileBinary = buffer.getBytes();
FileEntity file = new FileEntity();
file.setFile_name(fileName);
file.setMime_type(mimeType);
file.setUploaded_by(uploadedBy);
file.setFileName(fileName);
file.setMimeType(mimeType);
file.setUploadedBy(uploadedBy);
file.setContext(CoreFileContext.fromInt(contextValue));
fileService.create(file, fileBinary)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
} catch (Exception e) {
Constants.LOGGER.error(e.getMessage(), e);
LOGGER.error(e.getMessage(), e);
JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
}
}
public void update(RoutingContext ctx) {
FileEntity file = Constants.GSON.fromJson(ctx.body().asString(), FileEntity.class);
FileEntity file = GSON.fromJson(ctx.body().asString(), FileEntity.class);
fileService.update(file)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.OK, result))
@@ -82,14 +90,14 @@ public class FileDataHandler {
}
public void delete(RoutingContext ctx) {
Integer fileId = Integer.parseInt(ctx.pathParam("file_id"));
UUID fileId = UUID.fromString(ctx.pathParam("file_id"));
JsonObject body = ctx.body().asJsonObject();
String filePath = body.getString("file_path");
try {
Files.deleteIfExists(Paths.get(filePath));
} catch (IOException e) {
Constants.LOGGER.error(e.getMessage(), e);
LOGGER.error(e.getMessage(), e);
JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
return;
}

View File

@@ -1,9 +1,11 @@
package net.miarma.api.backlib.core.handlers;
package net.miarma.api.microservices.core.handlers;
import java.util.UUID;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.util.JsonUtil;
@@ -11,6 +13,8 @@ import net.miarma.api.backlib.util.JsonUtil;
public class FileLogicHandler {
private final Vertx vertx;
private final String CORE_EVENT_BUS = ConfigManager.getInstance()
.getStringProperty("eventbus.core.address");
public FileLogicHandler(Vertx vertx) {
this.vertx = vertx;
@@ -25,14 +29,14 @@ public class FileLogicHandler {
}
String token = authHeader.substring(7);
int userId = JWTManager.getInstance().getUserId(token);
UUID userId = JWTManager.getInstance().extractUserId(token);
if (userId <= 0) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token");
if (userId == null) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid or expired token");
return false;
}
request.put("userId", userId);
request.put("userId", userId.toString());
return true;
}
@@ -40,7 +44,7 @@ public class FileLogicHandler {
JsonObject request = new JsonObject().put("action", "getUserFiles");
if (!validateAuth(ctx, request)) return;
vertx.eventBus().request(Constants.CORE_EVENT_BUS, request, ar -> {
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
@@ -50,13 +54,15 @@ public class FileLogicHandler {
}
public void downloadFile(RoutingContext ctx) {
UUID fileId = UUID.fromString(ctx.pathParam("file_id"));
JsonObject request = new JsonObject()
.put("action", "downloadFile")
.put("fileId", Integer.parseInt(ctx.pathParam("file_id")));
.put("fileId", fileId.toString());
if (!validateAuth(ctx, request)) return;
vertx.eventBus().request(Constants.CORE_EVENT_BUS, request, ar -> {
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {

View File

@@ -1,4 +1,4 @@
package net.miarma.api.backlib.core.handlers;
package net.miarma.api.microservices.core.handlers;
import io.vertx.core.Vertx;
import io.vertx.ext.web.RoutingContext;

View File

@@ -0,0 +1,54 @@
package net.miarma.api.microservices.core.handlers;
import java.util.UUID;
import com.google.gson.Gson;
import io.vertx.core.Future;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.services.UserService;
@SuppressWarnings("unused")
public class UserDataHandler {
private final Gson GSON = GsonProvider.get();
private final UserService userService;
public UserDataHandler(Pool pool) {
this.userService = new UserService(pool);
}
public void getAll(RoutingContext ctx) {
userService.getAll(QueryParams.from(ctx))
.onSuccess(users -> JsonUtil.sendJson(ctx, ApiStatus.OK, users))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void getById(RoutingContext ctx) {
Future.succeededFuture(ctx.pathParam("user_id"))
.map(UUID::fromString)
.compose(userService::getById)
.onSuccess(user -> JsonUtil.sendJson(ctx, ApiStatus.OK, user))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Invalid UUID format"));
}
public void update(RoutingContext ctx) {
Future.succeededFuture(ctx.body().asString())
.map(body -> GSON.fromJson(body, UserEntity.class))
.compose(userService::update)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.OK, result))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Invalid user data"));
}
public void delete(RoutingContext ctx) {
Future.succeededFuture(ctx.pathParam("user_id"))
.map(UUID::fromString)
.compose(userService::delete)
.onSuccess(v -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Invalid UUID format"));
}
}

View File

@@ -0,0 +1,150 @@
package net.miarma.api.microservices.core.handlers;
import java.util.UUID;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.JsonUtil;
public class UserLogicHandler {
private final String CORE_EVENT_BUS = ConfigManager.getInstance()
.getStringProperty("eventbus.core.address");
private final Vertx vertx;
public UserLogicHandler(Vertx vertx) {
this.vertx = vertx;
}
public void getInfo(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Unauthorized");
return;
}
String token = authHeader.substring(7);
UUID userId = JWTManager.getInstance().extractUserId(token);
if (userId == null) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token");
return;
}
JsonObject request = new JsonObject()
.put("action", "getInfo")
.put("userId", userId);
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void exists(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "userExists")
.put("userId", userId);
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void getStatus(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "getStatus")
.put("userId", userId);
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void getRole(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "getRole")
.put("userId", userId);
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void getAvatar(RoutingContext ctx) {
int userId = Integer.parseInt(ctx.pathParam("user_id"));
JsonObject request = new JsonObject()
.put("action", "getAvatar")
.put("userId", userId);
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void updateStatus(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "updateStatus")
.put("userId", body.getInteger("userId"))
.put("status", body.getInteger("status"));
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null);
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
public void updateRole(RoutingContext ctx) {
JsonObject body = ctx.body().asJsonObject();
JsonObject request = new JsonObject()
.put("action", "updateRole")
.put("userId", body.getInteger("userId"))
.put("role", body.getInteger("role"));
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null);
} else {
EventBusUtil.handleReplyError(ctx, ar.cause());
}
});
}
}

View File

@@ -4,11 +4,11 @@ import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.core.handlers.FileDataHandler;
import net.miarma.api.backlib.core.handlers.UserDataHandler;
import net.miarma.api.backlib.core.services.UserService;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.handlers.FileDataHandler;
import net.miarma.api.microservices.core.handlers.UserDataHandler;
import net.miarma.api.microservices.core.routing.middlewares.CoreAuthGuard;
import net.miarma.api.microservices.core.services.UserService;
public class CoreDataRouter {
public static void mount(Router router, Vertx vertx, Pool pool) {
@@ -19,17 +19,15 @@ public class CoreDataRouter {
router.route().handler(BodyHandler.create());
router.get(CoreEndpoints.USERS).handler(authGuard.check(CoreUserRole.ADMIN)).handler(hUserData::getAll);
router.get(CoreEndpoints.USER).handler(authGuard.check(CoreUserRole.ADMIN)).handler(hUserData::getById);
router.post(CoreEndpoints.USERS).handler(hUserData::create);
router.put(CoreEndpoints.USER).handler(authGuard.check(CoreUserRole.ADMIN)).handler(hUserData::update);
router.delete(CoreEndpoints.USER).handler(authGuard.check(CoreUserRole.ADMIN)).handler(hUserData::delete);
router.get(CoreEndpoints.USERS).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hUserData::getAll);
router.get(CoreEndpoints.USER).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hUserData::getById);
router.put(CoreEndpoints.USER).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hUserData::update);
router.delete(CoreEndpoints.USER).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hUserData::delete);
router.get(CoreEndpoints.FILES).handler(authGuard.check()).handler(hFileData::getAll);
router.get(CoreEndpoints.FILES).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hFileData::getAll);
router.get(CoreEndpoints.FILE).handler(authGuard.check()).handler(hFileData::getById);
router.post(CoreEndpoints.FILE_UPLOAD).handler(authGuard.check()).handler(hFileData::create);
router.put(CoreEndpoints.FILE).handler(authGuard.check()).handler(hFileData::update);
router.delete(CoreEndpoints.FILE).handler(authGuard.check()).handler(hFileData::delete);
}
}

View File

@@ -1,39 +1,36 @@
package net.miarma.api.microservices.core.routing;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
public class CoreEndpoints {
/*
* RUTAS DE LA API DE DATOS
* DE NEGOCIO DEL SSO
*/
private static final ConfigManager config = ConfigManager.getInstance();
private static final String CORE = config.getApiPrefix("core");
private static final String AUTH = config.getApiPrefix("auth");
// Usuarios
public static final String USERS = Constants.CORE_PREFIX + "/users"; // GET, POST, PUT, DELETE
public static final String USER = Constants.CORE_PREFIX + "/users/:user_id"; // GET, PUT, DELETE
public static final String USER_STATUS = Constants.CORE_PREFIX + "/users/:user_id/status"; // GET, PUT
public static final String USER_ROLE = Constants.CORE_PREFIX + "/users/:user_id/role"; // GET, PUT
public static final String USER_EXISTS = Constants.CORE_PREFIX + "/users/:user_id/exists"; // GET
public static final String USER_AVATAR = Constants.CORE_PREFIX + "/users/:user_id/avatar"; // GET, PUT
public static final String USER_INFO = Constants.CORE_PREFIX + "/users/me"; // GET
/* API DE DATOS (UserDataHandler / FileDataHandler) */
public static final String USERS = CORE + "/users";
public static final String USER = CORE + "/users/:user_id";
public static final String FILES = CORE + "/files";
public static final String FILE = CORE + "/files/:file_id";
public static final String FILE_UPLOAD = CORE + "/files/upload";
// Archivos
public static final String FILES = Constants.CORE_PREFIX + "/files"; // GET, POST
public static final String FILE = Constants.CORE_PREFIX + "/files/:file_id"; // GET, PUT, DELETE
public static final String FILE_UPLOAD = Constants.CORE_PREFIX + "/files/upload"; // POST
public static final String FILE_DOWNLOAD = Constants.CORE_PREFIX + "/files/:file_id/download"; // GET
public static final String USER_FILES = Constants.CORE_PREFIX + "/files/myfiles"; // GET
/* API DE LÓGICA (AuthHandler / UserLogicHandler / FileLogicHandler) */
public static final String LOGIN = AUTH + "/login";
public static final String LOGIN_VALID = AUTH + "/login/validate";
public static final String REGISTER = AUTH + "/register";
public static final String CHANGE_PASSWORD = AUTH + "/change-password";
public static final String VALIDATE_TOKEN = AUTH + "/validate-token";
public static final String REFRESH_TOKEN = AUTH + "/refresh-token";
/*
* RUTAS DE LA API DE LOGICA
* DE NEGOCIO DEL SSO
*/
public static final String LOGIN = Constants.AUTH_PREFIX + "/login"; // POST
public static final String LOGIN_VALID = Constants.AUTH_PREFIX + "/login/validate"; // POST
public static final String REGISTER = Constants.AUTH_PREFIX + "/register"; // POST
public static final String CHANGE_PASSWORD = Constants.AUTH_PREFIX + "/change-password"; // POST
public static final String VALIDATE_TOKEN = Constants.AUTH_PREFIX + "/validate-token"; // POST
public static final String REFRESH_TOKEN = Constants.AUTH_PREFIX + "/refresh-token"; // POST
public static final String SCREENSHOT = Constants.CORE_PREFIX + "/screenshot"; // GET
public static final String USER_INFO = CORE + "/users/me";
public static final String USER_STATUS = CORE + "/users/:user_id/status";
public static final String USER_ROLE = CORE + "/users/:user_id/role";
public static final String USER_EXISTS = CORE + "/users/:user_id/exists";
public static final String USER_AVATAR = CORE + "/users/:user_id/avatar";
public static final String FILE_DOWNLOAD = CORE + "/files/:file_id/download";
public static final String USER_FILES = CORE + "/files/myfiles";
public static final String SCREENSHOT = CORE + "/screenshot";
}

View File

@@ -4,36 +4,40 @@ import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.core.handlers.FileLogicHandler;
import net.miarma.api.backlib.core.handlers.ScreenshotHandler;
import net.miarma.api.backlib.core.handlers.UserLogicHandler;
import net.miarma.api.backlib.core.services.UserService;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.handlers.AuthHandler;
import net.miarma.api.microservices.core.handlers.FileLogicHandler;
import net.miarma.api.microservices.core.handlers.ScreenshotHandler;
import net.miarma.api.microservices.core.handlers.UserLogicHandler;
import net.miarma.api.microservices.core.routing.middlewares.CoreAuthGuard;
import net.miarma.api.microservices.core.services.UserService;
public class CoreLogicRouter {
public static void mount(Router router, Vertx vertx, Pool pool) {
UserLogicHandler hUserLogic = new UserLogicHandler(vertx);
FileLogicHandler hFileLogic = new FileLogicHandler(vertx);
AuthHandler hAuth = new AuthHandler(vertx);
ScreenshotHandler hScreenshot = new ScreenshotHandler(vertx);
UserService userService = new UserService(pool);
CoreAuthGuard authGuard = new CoreAuthGuard(userService);
router.route().handler(BodyHandler.create());
router.post(CoreEndpoints.LOGIN).handler(hUserLogic::login);
router.get(CoreEndpoints.USER_INFO).handler(authGuard.check()).handler(hUserLogic::getInfo);
router.post(CoreEndpoints.REGISTER).handler(hUserLogic::register);
router.post(CoreEndpoints.CHANGE_PASSWORD).handler(authGuard.check()).handler(hUserLogic::changePassword);
router.post(CoreEndpoints.LOGIN_VALID).handler(hUserLogic::loginValidate);
router.get(CoreEndpoints.VALIDATE_TOKEN).handler(hUserLogic::validateToken);
router.get(CoreEndpoints.REFRESH_TOKEN).handler(hUserLogic::refreshToken);
router.post(CoreEndpoints.LOGIN).handler(hAuth::login);
router.post(CoreEndpoints.REGISTER).handler(hAuth::register);
router.post(CoreEndpoints.LOGIN_VALID).handler(hAuth::loginValidate);
router.get(CoreEndpoints.VALIDATE_TOKEN).handler(authGuard.check()).handler(hAuth::validateToken);
router.get(CoreEndpoints.REFRESH_TOKEN).handler(hAuth::refreshToken);
router.post(CoreEndpoints.CHANGE_PASSWORD).handler(authGuard.check()).handler(hAuth::changePassword);
router.get(CoreEndpoints.USER_INFO).handler(authGuard.check()).handler(hUserLogic::getInfo);
router.get(CoreEndpoints.USER_EXISTS).handler(authGuard.check()).handler(hUserLogic::exists);
router.get(CoreEndpoints.USER_STATUS).handler(authGuard.check()).handler(hUserLogic::getStatus);
router.put(CoreEndpoints.USER_STATUS).handler(authGuard.check(CoreUserRole.ADMIN)).handler(hUserLogic::updateStatus);
router.put(CoreEndpoints.USER_STATUS).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hUserLogic::updateStatus);
router.get(CoreEndpoints.USER_ROLE).handler(authGuard.check()).handler(hUserLogic::getRole);
router.put(CoreEndpoints.USER_ROLE).handler(authGuard.check(CoreUserRole.ADMIN)).handler(hUserLogic::updateRole);
router.put(CoreEndpoints.USER_ROLE).handler(authGuard.check(CoreUserGlobalRole.ADMIN)).handler(hUserLogic::updateRole);
router.get(CoreEndpoints.USER_AVATAR).handler(authGuard.check()).handler(hUserLogic::getAvatar);
router.get(CoreEndpoints.FILE_DOWNLOAD).handler(authGuard.check()).handler(hFileLogic::downloadFile);

View File

@@ -1,14 +1,15 @@
package net.miarma.api.microservices.core.routing.middlewares;
import java.util.UUID;
import java.util.function.Consumer;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.middlewares.AbstractAuthGuard;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.backlib.core.services.UserService;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.services.UserService;
public class CoreAuthGuard extends AbstractAuthGuard<UserEntity, CoreUserRole> {
public class CoreAuthGuard extends AbstractAuthGuard<UserEntity, CoreUserGlobalRole> {
private final UserService userService;
public CoreAuthGuard(UserService userService) {
@@ -16,12 +17,12 @@ public class CoreAuthGuard extends AbstractAuthGuard<UserEntity, CoreUserRole> {
}
@Override
protected CoreUserRole parseRole(String roleStr) {
return CoreUserRole.valueOf(roleStr.toUpperCase());
protected CoreUserGlobalRole parseRole(String roleStr) {
return CoreUserGlobalRole.valueOf(roleStr.toUpperCase());
}
@Override
protected void getUserEntity(int userId, RoutingContext ctx, Consumer<UserEntity> callback) {
protected void getUserEntity(UUID userId, RoutingContext ctx, Consumer<UserEntity> callback) {
userService.getById(userId).onComplete(ar -> {
if (ar.succeeded()) callback.accept(ar.result());
else callback.accept(null);
@@ -29,8 +30,8 @@ public class CoreAuthGuard extends AbstractAuthGuard<UserEntity, CoreUserRole> {
}
@Override
protected boolean hasPermission(UserEntity user, CoreUserRole userRole) {
return user.getGlobal_role() == CoreUserRole.ADMIN;
protected boolean hasPermission(UserEntity user, CoreUserGlobalRole userRole) {
return user.getGlobalRole() == CoreUserGlobalRole.ADMIN;
}
}

View File

@@ -0,0 +1,91 @@
package net.miarma.api.microservices.core.services;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.exceptions.BadRequestException;
import net.miarma.api.backlib.exceptions.ForbiddenException;
import net.miarma.api.backlib.exceptions.NotFoundException;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.security.PasswordHasher;
import net.miarma.api.microservices.core.dao.CredentialDAO;
import net.miarma.api.microservices.core.dao.UserDAO;
import net.miarma.api.microservices.core.entities.CredentialEntity;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.enums.CoreUserGlobalStatus;
public class AuthService {
private final UserDAO userDAO;
private final CredentialDAO credentialDAO;
private final int serviceId;
public AuthService(Pool pool, int serviceId) {
this.userDAO = new UserDAO(pool);
this.credentialDAO = new CredentialDAO(pool);
this.serviceId = serviceId;
}
/**
* Login: Comprueba credenciales y devuelve el token + datos del usuario
*/
public Future<JsonObject> login(String login, String plainPassword, boolean keepLoggedIn) {
return credentialDAO.getByServiceAndUsername(this.serviceId, login).compose(cred -> {
if (cred == null) {
return Future.failedFuture(new NotFoundException("User not found in this domain"));
}
if (!PasswordHasher.verify(plainPassword, cred.getPassword())) {
return Future.failedFuture(new BadRequestException("Invalid credentials"));
}
return userDAO.getById(cred.getUserId()).compose(user -> {
if (user == null) {
return Future.failedFuture(new NotFoundException("User not found"));
}
if (user.getGlobalStatus() != CoreUserGlobalStatus.ACTIVE) {
return Future.failedFuture(new ForbiddenException("User is inactive"));
}
String token = JWTManager.getInstance().generateToken(
cred.getUsername(),
user.getUserId(),
user.getGlobalRole(),
this.serviceId,
keepLoggedIn
);
JsonObject response = new JsonObject()
.put("token", token)
.put("user", JsonObject.mapFrom(user));
return Future.succeededFuture(response);
});
});
}
/**
* Register: Crea el perfil global y la credencial de servicio
*/
public Future<UserEntity> register(UserEntity profile, String username, String email, String password) {
profile.setUserId(UUID.randomUUID());
profile.setGlobalRole(CoreUserGlobalRole.USER);
profile.setGlobalStatus(CoreUserGlobalStatus.ACTIVE);
return userDAO.insert(profile).compose(savedUser -> {
CredentialEntity cred = new CredentialEntity();
cred.setCredentialId(UUID.randomUUID());
cred.setUserId(savedUser.getUserId());
cred.setServiceId(this.serviceId);
cred.setUsername(username);
cred.setEmail(email);
cred.setPassword(PasswordHasher.hash(password));
cred.setStatus(1);
return credentialDAO.insert(cred).map(savedUser);
});
}
}

View File

@@ -1,27 +1,35 @@
package net.miarma.api.backlib.core.services;
import io.vertx.core.Future;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.OSType;
import net.miarma.api.backlib.exceptions.NotFoundException;
import net.miarma.api.backlib.exceptions.ValidationException;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.core.dao.FileDAO;
import net.miarma.api.backlib.core.entities.FileEntity;
import net.miarma.api.backlib.core.validators.FileValidator;
package net.miarma.api.microservices.core.services;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import org.slf4j.Logger;
import com.google.gson.Gson;
import io.vertx.core.Future;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.config.OSType;
import net.miarma.api.backlib.exceptions.NotFoundException;
import net.miarma.api.backlib.exceptions.ValidationException;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.microservices.core.dao.FileDAO;
import net.miarma.api.microservices.core.entities.FileEntity;
import net.miarma.api.microservices.core.validators.FileValidator;
public class FileService {
private final FileDAO fileDAO;
private final FileValidator fileValidator;
private final Gson GSON = GsonProvider.get();
private final Logger LOGGER = LoggerProvider.getLogger();
public FileService(Pool pool) {
this.fileDAO = new FileDAO(pool);
@@ -32,7 +40,7 @@ public class FileService {
return fileDAO.getAll(params);
}
public Future<FileEntity> getById(Integer id) {
public Future<FileEntity> getById(UUID id) {
return fileDAO.getById(id).compose(file -> {
if (file == null) {
return Future.failedFuture(new NotFoundException("File not found with id: " + id));
@@ -41,28 +49,28 @@ public class FileService {
});
}
public Future<List<FileEntity>> getUserFiles(Integer userId) {
public Future<List<FileEntity>> getUserFiles(UUID userId) {
return fileDAO.getUserFiles(userId);
}
public Future<FileEntity> create(FileEntity file, byte[] fileBinary) {
return fileValidator.validate(file, fileBinary.length).compose(validation -> {
return fileValidator.validateAsync(file, fileBinary.length).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
return Future.failedFuture(new ValidationException(GSON.toJson(validation.getErrors())));
}
String dir = ConfigManager.getInstance()
.getFilesDir(file.getContext().toCtxString());
String pathString = dir + file.getFile_name();
Path filePath = Paths.get(dir + file.getFile_name());
file.setFile_path(ConfigManager.getOS() == OSType.WINDOWS ?
String pathString = dir + file.getFileName();
Path filePath = Paths.get(dir + file.getFileName());
file.setFilePath(ConfigManager.getOS() == OSType.WINDOWS ?
pathString.replace("\\", "\\\\") : pathString);
try {
Files.write(filePath, fileBinary);
} catch (IOException e) {
Constants.LOGGER.error("Error writing file to disk: ", e);
LOGGER.error("Error writing file to disk: ", e);
return Future.failedFuture(e);
}
@@ -70,14 +78,14 @@ public class FileService {
});
}
public Future<FileEntity> downloadFile(Integer fileId) {
public Future<FileEntity> downloadFile(UUID fileId) {
return getById(fileId);
}
public Future<FileEntity> update(FileEntity file) {
return fileValidator.validate(file).compose(validation -> {
return fileValidator.validateAsync(file).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
return Future.failedFuture(new ValidationException(GSON.toJson(validation.getErrors())));
}
return fileDAO.update(file);
@@ -85,27 +93,27 @@ public class FileService {
}
public Future<FileEntity> upsert(FileEntity file) {
return fileValidator.validate(file).compose(validation -> {
return fileValidator.validateAsync(file).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
return Future.failedFuture(new ValidationException(GSON.toJson(validation.getErrors())));
}
return fileDAO.upsert(file, "file_id");
});
}
public Future<Boolean> delete(Integer fileId) {
public Future<Boolean> delete(UUID fileId) {
return getById(fileId).compose(file -> {
String dir = ConfigManager.getInstance()
.getFilesDir(file.getContext().toCtxString());
String filePath = dir + file.getFile_name();
String filePath = dir + file.getFileName();
Path path = Paths.get(filePath);
try {
Files.deleteIfExists(path);
} catch (IOException e) {
Constants.LOGGER.error("Error deleting file from disk: ", e);
LOGGER.error("Error deleting file from disk: ", e);
return Future.failedFuture(e);
}
@@ -118,7 +126,7 @@ public class FileService {
});
}
public Future<Boolean> exists(Integer fileId) {
public Future<Boolean> exists(UUID fileId) {
return fileDAO.exists(fileId);
}
}

View File

@@ -0,0 +1,55 @@
package net.miarma.api.microservices.core.services;
import java.util.List;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.exceptions.NotFoundException;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.core.dao.UserDAO;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.enums.CoreUserGlobalStatus;
public class UserService {
private final UserDAO userDAO;
public UserService(Pool pool) {
this.userDAO = new UserDAO(pool);
}
public Future<List<UserEntity>> getAll(QueryParams params) {
return userDAO.getAll(params);
}
public Future<UserEntity> getById(UUID id) {
return userDAO.getById(id).compose(user -> {
if (user == null) return Future.failedFuture(new NotFoundException("User not found"));
return Future.succeededFuture(user);
});
}
public Future<UserEntity> update(UserEntity user) {
return userDAO.update(user);
}
public Future<UserEntity> changeRole(UUID userId, CoreUserGlobalRole newRole) {
return getById(userId).compose(user -> {
user.setGlobalRole(newRole);
return userDAO.update(user);
});
}
public Future<UserEntity> changeStatus(UUID userId, CoreUserGlobalStatus newStatus) {
return getById(userId).compose(user -> {
user.setGlobalStatus(newStatus);
return userDAO.update(user);
});
}
public Future<Boolean> delete(UUID id) {
return userDAO.delete(id);
}
}

View File

@@ -0,0 +1,63 @@
package net.miarma.api.microservices.core.validators;
import io.vertx.core.Future;
import net.miarma.api.backlib.validation.ValidationResult;
import net.miarma.api.microservices.core.entities.FileEntity;
public class FileValidator {
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
public Future<ValidationResult> validateAsync(FileEntity file, long size) {
ValidationResult result = validate(file);
if (size <= 0) {
result.addError("size", "File size must be greater than 0 bytes");
}
if (size > MAX_FILE_SIZE) {
result.addError("size", "File size exceeds the 10 MB limit");
}
return Future.succeededFuture(result);
}
public Future<ValidationResult> validateAsync(FileEntity file) {
ValidationResult result = validate(file);
return Future.succeededFuture(result);
}
private ValidationResult validate(FileEntity file) {
ValidationResult result = new ValidationResult();
if (file == null) {
return result.addError("file", "File object cannot be null");
}
if (file.getFileName() == null || file.getFileName().isBlank()) {
result.addError("file_name", "File name is required");
} else if (file.getFileName().length() > 255) {
result.addError("file_name", "File name is too long (max 255 characters)");
}
if (file.getFilePath() == null || file.getFilePath().isBlank()) {
result.addError("file_path", "File path is required");
} else if (file.getFilePath().length() > 255) {
result.addError("file_path", "File path is too long (max 255 characters)");
}
if (file.getMimeType() == null || file.getMimeType().isBlank()) {
result.addError("mime_type", "MIME type is required");
}
if (file.getContext() == null) {
result.addError("context", "File context is required");
}
if (file.getUploadedBy() == null) {
result.addError("uploaded_by", "Uploader User ID is required");
}
return result;
}
}

View File

@@ -0,0 +1,45 @@
package net.miarma.api.microservices.core.validators;
import io.vertx.core.Future;
import net.miarma.api.backlib.validation.ValidationResult;
import net.miarma.api.microservices.core.entities.CredentialEntity;
import net.miarma.api.microservices.core.entities.UserEntity;
public class UserValidator {
public Future<ValidationResult> validateAsync(UserEntity user, CredentialEntity creds) {
ValidationResult result = new ValidationResult();
if (user == null) {
return Future.succeededFuture(result.addError("user", "User profile cannot be null"));
}
if (user.getDisplayName() == null || user.getDisplayName().isBlank()) {
result.addError("display_name", "Display name is required");
}
if (creds == null) {
return Future.succeededFuture(result.addError("credentials", "Credentials are required"));
}
if (creds.getUsername() == null || creds.getUsername().isBlank()) {
result.addError("username", "Username is required");
}
if (creds.getEmail() != null && !creds.getEmail().isBlank()) {
if (!creds.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
result.addError("email", "Invalid email format");
}
} else {
result.addError("email", "Email is required");
}
if (creds.getPassword() == null || creds.getPassword().isBlank()) {
result.addError("password", "Password is required");
} else if (creds.getPassword().length() < 6) {
result.addError("password", "Password must be at least 6 characters long");
}
return Future.succeededFuture(result);
}
}

View File

@@ -1,196 +1,123 @@
package net.miarma.api.microservices.core.verticles;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.Constants.CoreUserGlobalStatus;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.backlib.core.services.FileService;
import net.miarma.api.backlib.core.services.UserService;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.RouterUtil;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.enums.CoreUserGlobalRole;
import net.miarma.api.microservices.core.enums.CoreUserGlobalStatus;
import net.miarma.api.microservices.core.routing.CoreDataRouter;
import net.miarma.api.microservices.core.routing.CoreEndpoints;
import net.miarma.api.microservices.core.services.AuthService;
import net.miarma.api.microservices.core.services.FileService;
import net.miarma.api.microservices.core.services.UserService;
@SuppressWarnings("unused")
public class CoreDataVerticle extends AbstractVerticle {
private ConfigManager configManager;
private UserService userService;
private FileService fileService;
private AuthService authService;
@Override
public void start(Promise<Void> startPromise) {
configManager = ConfigManager.getInstance();
Pool pool = DatabaseProvider.createPool(vertx, configManager);
userService = new UserService(pool);
fileService = new FileService(pool);
authService = new AuthService(pool, 0);
Router router = Router.router(vertx);
RouterUtil.attachLogger(router);
CoreDataRouter.mount(router, vertx, pool);
registerLogicVerticleConsumer();
vertx.createHttpServer()
.requestHandler(router)
.listen(configManager.getIntProperty("sso.data.port"), res -> {
.listen(configManager.getIntProperty("core.data.port"), res -> {
if (res.succeeded()) startPromise.complete();
else startPromise.fail(res.cause());
});
}
private void registerLogicVerticleConsumer() {
vertx.eventBus().consumer(Constants.AUTH_EVENT_BUS, message -> {
String authAddress = configManager.getStringProperty("eventbus.auth.address");
vertx.eventBus().consumer(authAddress, message -> {
JsonObject body = (JsonObject) message.body();
String action = body.getString("action");
try {
switch (action) {
case "login" -> {
String email = body.getString("email");
String userName = body.getString("userName");
String password = body.getString("password");
boolean keepLoggedIn = body.getBoolean("keepLoggedIn", false);
userService.login(email != null ? email : userName, password, keepLoggedIn)
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
authService.login(
body.getString("login"),
body.getString("password"),
body.getBoolean("keepLoggedIn", false)
).onSuccess(message::reply).onFailure(EventBusUtil.fail(message));
}
case "register" -> {
UserEntity user = new UserEntity();
user.setUser_name(body.getString("userName"));
user.setEmail(body.getString("email"));
user.setDisplay_name(body.getString("displayName"));
user.setPassword(body.getString("password"));
UserEntity profile = new UserEntity();
profile.setDisplayName(body.getString("displayName"));
userService.register(user)
authService.register(
profile,
body.getString("username"),
body.getString("email"),
body.getString("password")
).onSuccess(user -> message.reply(JsonObject.mapFrom(user)))
.onFailure(EventBusUtil.fail(message));
}
case "getInfo", "getById", "getUserById" -> {
userService.getById(UUID.fromString(body.getString("userId")))
.onSuccess(user -> message.reply(JsonObject.mapFrom(user)))
.onFailure(EventBusUtil.fail(message));
}
case "updateStatus" -> {
userService.changeStatus(
UUID.fromString(body.getString("userId")),
CoreUserGlobalStatus.fromInt(body.getInteger("status"))
).onSuccess(res -> message.reply(new JsonObject().put("message", "Status updated")))
.onFailure(EventBusUtil.fail(message));
}
case "updateRole" -> {
userService.changeRole(
UUID.fromString(body.getString("userId")),
CoreUserGlobalRole.fromInt(body.getInteger("role"))
).onSuccess(res -> message.reply(new JsonObject().put("message", "Role updated")))
.onFailure(EventBusUtil.fail(message));
}
case "getUserFiles" -> {
fileService.getUserFiles(UUID.fromString(body.getString("userId")))
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
}
case "changePassword" -> {
Integer userId = body.getInteger("userId");
String newPassword = body.getString("newPassword");
userService.changePassword(userId, newPassword)
.onSuccess(user -> {
String userJson = Constants.GSON.toJson(user);
message.reply(new JsonObject(userJson));
})
.onFailure(EventBusUtil.fail(message));
}
case "validateToken" -> {
String token = body.getString("token");
userService.validateToken(token)
case "downloadFile" -> {
fileService.getById(UUID.fromString(body.getString("fileId")))
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
}
case "getInfo", "getById" -> {
Integer userId = body.getInteger("userId");
userService.getById(userId)
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
default -> message.fail(404, "Action not found: " + action);
}
case "userExists" -> {
Integer userId = body.getInteger("userId");
userService.getById(userId)
.onSuccess(user -> {
Map<String, Object> result = new HashMap<>();
result.put("user_id", userId);
result.put("exists", user != null);
message.reply(result);
})
.onFailure(EventBusUtil.fail(message));
}
case "getByEmail" -> userService.getByEmail(body.getString("email"))
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
case "getByUserName" -> userService.getByUserName(body.getString("userName"))
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
case "getStatus" -> userService.getById(body.getInteger("userId"))
.onSuccess(user -> {
Map<String, Object> result = new HashMap<>();
result.put("user_id", user.getUser_id());
result.put("status", user.getGlobal_status());
message.reply(result);
})
.onFailure(EventBusUtil.fail(message));
case "getRole" -> userService.getById(body.getInteger("userId"))
.onSuccess(user -> {
Map<String, Object> result = new HashMap<>();
result.put("user_id", user.getUser_id());
result.put("role", user.getGlobal_role());
message.reply(result);
})
.onFailure(EventBusUtil.fail(message));
case "getAvatar" -> userService.getById(body.getInteger("userId"))
.onSuccess(user -> {
Map<String, Object> result = new HashMap<>();
result.put("user_id", user.getUser_id());
result.put("avatar", user.getAvatar());
message.reply(result);
})
.onFailure(EventBusUtil.fail(message));
case "updateStatus" -> userService.updateStatus(
body.getInteger("userId"),
CoreUserGlobalStatus.fromInt(body.getInteger("status")))
.onSuccess(res -> message.reply("Status updated successfully"))
.onFailure(EventBusUtil.fail(message));
case "updateRole" -> userService.updateRole(
body.getInteger("userId"),
CoreUserRole.fromInt(body.getInteger("role")))
.onSuccess(res -> message.reply("Role updated successfully"))
.onFailure(EventBusUtil.fail(message));
case "getUserFiles" -> fileService.getUserFiles(body.getInteger("userId"))
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
case "downloadFile" -> fileService.downloadFile(body.getInteger("fileId"))
.onSuccess(message::reply)
.onFailure(EventBusUtil.fail(message));
case "getUserById" -> userService.getById(body.getInteger("userId"))
.onSuccess(user -> {
String userJson = Constants.GSON.toJson(user);
message.reply(new JsonObject(userJson));
})
.onFailure(EventBusUtil.fail(message));
case "loginValidate" -> {
Integer userId = body.getInteger("userId");
String password = body.getString("password");
userService.loginValidate(userId, password)
.onSuccess(user -> {
String userJson = Constants.GSON.toJson(user);
message.reply(new JsonObject(userJson));
})
.onFailure(EventBusUtil.fail(message));
}
default -> EventBusUtil.fail(message);
} catch (Exception e) {
message.fail(400, "Invalid data format or UUID: " + e.getMessage());
}
});
}

View File

@@ -5,7 +5,7 @@ import io.vertx.core.Promise;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.RouterUtil;
import net.miarma.api.microservices.core.routing.CoreLogicRouter;
@@ -24,7 +24,7 @@ public class CoreLogicVerticle extends AbstractVerticle {
vertx.createHttpServer()
.requestHandler(router)
.listen(configManager.getIntProperty("sso.logic.port"), res -> {
.listen(configManager.getIntProperty("core.logic.port"), res -> {
if (res.succeeded()) startPromise.complete();
else startPromise.fail(res.cause());
});

View File

@@ -1,16 +1,19 @@
package net.miarma.api.microservices.core.verticles;
import org.slf4j.Logger;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Promise;
import io.vertx.core.ThreadingModel;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.LogAccumulator;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.log.LogAccumulator;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.backlib.util.DeploymentUtil;
public class CoreMainVerticle extends AbstractVerticle {
private final Logger LOGGER = LoggerProvider.getLogger();
private ConfigManager configManager;
@Override
@@ -20,7 +23,7 @@ public class CoreMainVerticle extends AbstractVerticle {
deployVerticles();
startPromise.complete();
} catch (Exception e) {
Constants.LOGGER.error(DeploymentUtil.failMessage(CoreMainVerticle.class, e));
LOGGER.error(DeploymentUtil.failMessage(CoreMainVerticle.class, e));
startPromise.fail(e);
}
}
@@ -35,7 +38,7 @@ public class CoreMainVerticle extends AbstractVerticle {
DeploymentUtil.successMessage(CoreDataVerticle.class),
DeploymentUtil.apiUrlMessage(
configManager.getHost(),
configManager.getIntProperty("sso.data.port")
configManager.getIntProperty("core.data.port")
)
);
LogAccumulator.add(message);
@@ -50,7 +53,7 @@ public class CoreMainVerticle extends AbstractVerticle {
DeploymentUtil.successMessage(CoreLogicVerticle.class),
DeploymentUtil.apiUrlMessage(
configManager.getHost(),
configManager.getIntProperty("sso.logic.port")
configManager.getIntProperty("core.logic.port")
)
);
LogAccumulator.add(message);

View File

@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma.api</groupId>
<artifactId>huertos</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
@@ -20,7 +20,7 @@
<dependency>
<groupId>net.miarma.api</groupId>
<artifactId>backlib</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,27 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum HuertosAnnouncementPriority implements IValuableEnum {
LOW(0),
MEDIUM(1),
HIGH(2);
private final int value;
HuertosAnnouncementPriority(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosAnnouncementPriority fromInt(int i) {
for (HuertosAnnouncementPriority priority : values()) {
if (priority.value == i) return priority;
}
throw new IllegalArgumentException("Invalid HuertosAnnouncementPriority value: " + i);
}
}

View File

@@ -0,0 +1,26 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum HuertosPaymentFrequency implements IValuableEnum {
BIYEARLY(0),
YEARLY(1);
private final int value;
HuertosPaymentFrequency(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosPaymentFrequency fromInt(int i) {
for (HuertosPaymentFrequency frequency : values()) {
if (frequency.value == i) return frequency;
}
throw new IllegalArgumentException("Invalid HuertoPaymentFrequency value: " + i);
}
}

View File

@@ -0,0 +1,26 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum HuertosPaymentType implements IValuableEnum {
BANK(0),
CASH(1);
private final int value;
HuertosPaymentType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosPaymentType fromInt(int i) {
for (HuertosPaymentType type : values()) {
if (type.value == i) return type;
}
throw new IllegalArgumentException("Invalid HuertoPaymentType value: " + i);
}
}

View File

@@ -0,0 +1,27 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum HuertosRequestStatus implements IValuableEnum {
PENDING(0),
APPROVED(1),
REJECTED(2);
private final int value;
HuertosRequestStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosRequestStatus fromInt(int i) {
for (HuertosRequestStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid HuertoRequestStatus value: " + i);
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum HuertosRequestType implements IValuableEnum {
REGISTER(0),
UNREGISTER(1),
ADD_COLLABORATOR(2),
REMOVE_COLLABORATOR(3),
ADD_GREENHOUSE(4),
REMOVE_GREENHOUSE(5);
private final int value;
HuertosRequestType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosRequestType fromInt(int i) {
for (HuertosRequestType type : values()) {
if (type.value == i) return type;
}
throw new IllegalArgumentException("Invalid HuertoRequestType value: " + i);
}
}

View File

@@ -0,0 +1,27 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IUserRole;
public enum HuertosUserRole implements IUserRole {
USER(0),
ADMIN(1),
DEV(2);
private final int value;
HuertosUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosUserRole fromInt(int i) {
for (HuertosUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid HuertosUserRole value: " + i);
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.api.microservices.huertos.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum HuertosUserType implements IValuableEnum {
WAIT_LIST(0),
MEMBER(1),
WITH_GREENHOUSE(2),
COLLABORATOR(3),
DEVELOPER(5),
SUBSIDY(4);
private final int value;
HuertosUserType(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static HuertosUserType fromInt(int i) {
for (HuertosUserType type : values()) {
if (type.value == i) return type;
}
throw new IllegalArgumentException("Invalid HuertosUserType value: " + i);
}
}

View File

@@ -10,8 +10,8 @@ import io.vertx.core.WorkerExecutor;
import io.vertx.core.json.Json;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.microservices.huertos.entities.MemberEntity;

View File

@@ -14,7 +14,7 @@ import jakarta.mail.Session;
import jakarta.mail.Store;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeUtility;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.config.ConfigManager;
public class ImapReader {

View File

@@ -23,7 +23,7 @@ import jakarta.mail.Session;
import jakarta.mail.Store;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.config.ConfigManager;
public class MailManager {
private final Vertx vertx;
@@ -31,6 +31,9 @@ public class MailManager {
private final int smtpPort;
private final Map<String, MailClient> clientCache = new ConcurrentHashMap<>();
public static final List<String> HUERTOS_ALLOWED_MAIL_FOLDERS =
List.of("INBOX", "Drafts", "Sent", "Spam", "Trash");
public MailManager(Vertx vertx) {
this.vertx = vertx;
this.smtpHost = ConfigManager.getInstance().getStringProperty("smtp.server");

View File

@@ -9,10 +9,10 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.Constants.HuertosUserStatus;
import net.miarma.api.backlib.Constants.HuertosUserType;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.NameCensorer;

View File

@@ -5,7 +5,7 @@ import io.vertx.core.Promise;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.RouterUtil;
import net.miarma.api.microservices.huertos.routing.HuertosLogicRouter;

View File

@@ -2,9 +2,9 @@ package net.miarma.api.microservices.huertos.verticles;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.LogAccumulator;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.log.LogAccumulator;
import net.miarma.api.backlib.util.DeploymentUtil;
public class HuertosMainVerticle extends AbstractVerticle {

View File

@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma.api</groupId>
<artifactId>huertosdecine</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
@@ -20,7 +20,7 @@
<dependency>
<groupId>net.miarma.api</groupId>
<artifactId>backlib</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,26 @@
package net.miarma.api.microservices.huertosdecine.enums;
import net.miarma.api.backlib.interfaces.IUserRole;
public enum CineUserRole implements IUserRole {
USER(0),
ADMIN(1);
private final int value;
CineUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static CineUserRole fromInt(int i) {
for (CineUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid CineUserRole value: " + i);
}
}

View File

@@ -2,8 +2,8 @@ package net.miarma.api.microservices.huertosdecine.handlers;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.huertosdecine.entities.MovieEntity;

View File

@@ -9,8 +9,8 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.RouterUtil;

View File

@@ -5,7 +5,7 @@ import io.vertx.core.Promise;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.RouterUtil;
import net.miarma.api.microservices.huertosdecine.routing.CineLogicRouter;

View File

@@ -2,9 +2,9 @@ package net.miarma.api.microservices.huertosdecine.verticles;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.LogAccumulator;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.log.LogAccumulator;
import net.miarma.api.backlib.util.DeploymentUtil;
public class CineMainVerticle extends AbstractVerticle {

View File

@@ -1,8 +1,8 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma.api</groupId>
<artifactId>miarmacraft</artifactId>
<version>1.2.1</version>
<artifactId>minecraft</artifactId>
<version>2.0.0</version>
<properties>
<maven.compiler.source>23</maven.compiler.source>
@@ -20,7 +20,7 @@
<dependency>
<groupId>net.miarma.api</groupId>
<artifactId>backlib</artifactId>
<version>1.2.1</version>
<version>2.0.0</version>
</dependency>
</dependencies>

View File

@@ -1,5 +0,0 @@
package net.miarma.api.microservices.miarmacraft.handlers;
public class ModLogicHandler {
}

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.dao;
package net.miarma.api.microservices.minecraft.dao;
import io.vertx.core.Future;
import io.vertx.core.Promise;
@@ -8,7 +8,7 @@ import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.miarmacraft.entities.ModEntity;
import net.miarma.api.microservices.minecraft.entities.ModEntity;
import java.util.List;
import java.util.Map;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.dao;
package net.miarma.api.microservices.minecraft.dao;
import io.vertx.core.Future;
import io.vertx.core.Promise;
@@ -8,7 +8,7 @@ import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.miarmacraft.entities.PlayerEntity;
import net.miarma.api.microservices.minecraft.entities.PlayerEntity;
import java.util.List;
import java.util.Map;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.dao;
package net.miarma.api.microservices.minecraft.dao;
import io.vertx.core.Future;
import io.vertx.core.Promise;
@@ -8,7 +8,7 @@ import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.miarmacraft.entities.UserMetadataEntity;
import net.miarma.api.microservices.minecraft.entities.UserMetadataEntity;
import java.util.List;
import java.util.Map;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.entities;
package net.miarma.api.microservices.minecraft.entities;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.Constants.MMCModStatus;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.entities;
package net.miarma.api.microservices.minecraft.entities;
import java.time.LocalDateTime;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.entities;
package net.miarma.api.microservices.minecraft.entities;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.Constants.MMCUserRole;

View File

@@ -0,0 +1,26 @@
package net.miarma.api.microservices.minecraft.enums;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum MinecraftModStatus implements IValuableEnum {
ACTIVE(0),
INACTIVE(1);
private final int value;
MinecraftModStatus(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static MinecraftModStatus fromInt(int i) {
for (MinecraftModStatus status : values()) {
if (status.value == i) return status;
}
throw new IllegalArgumentException("Invalid MinecraftModStatus value: " + i);
}
}

View File

@@ -0,0 +1,27 @@
package net.miarma.api.microservices.minecraft.enums;
import net.miarma.api.backlib.interfaces.IUserRole;
import net.miarma.api.backlib.interfaces.IValuableEnum;
public enum MinecraftUserRole implements IUserRole, IValuableEnum {
PLAYER(0),
ADMIN(1);
private final int value;
MinecraftUserRole(int value) {
this.value = value;
}
@Override
public int getValue() {
return value;
}
public static MinecraftUserRole fromInt(int i) {
for (MinecraftUserRole role : values()) {
if (role.value == i) return role;
}
throw new IllegalArgumentException("Invalid MinecraftUserRole value: " + i);
}
}

View File

@@ -1,13 +1,13 @@
package net.miarma.api.microservices.miarmacraft.handlers;
package net.miarma.api.microservices.minecraft.handlers;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.microservices.miarmacraft.entities.ModEntity;
import net.miarma.api.microservices.miarmacraft.services.ModService;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.microservices.minecraft.entities.ModEntity;
import net.miarma.api.microservices.minecraft.services.ModService;
import java.nio.file.Files;
import java.nio.file.Path;

View File

@@ -0,0 +1,5 @@
package net.miarma.api.microservices.minecraft.handlers;
public class ModLogicHandler {
}

View File

@@ -1,12 +1,12 @@
package net.miarma.api.microservices.miarmacraft.handlers;
package net.miarma.api.microservices.minecraft.handlers;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.microservices.miarmacraft.entities.PlayerEntity;
import net.miarma.api.microservices.miarmacraft.services.PlayerService;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.microservices.minecraft.entities.PlayerEntity;
import net.miarma.api.microservices.minecraft.services.PlayerService;
public class PlayerDataHandler {
private final PlayerService playerService;

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.handlers;
package net.miarma.api.microservices.minecraft.handlers;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

View File

@@ -1,14 +1,14 @@
package net.miarma.api.microservices.miarmacraft.routing;
package net.miarma.api.microservices.minecraft.routing;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants.MMCUserRole;
import net.miarma.api.microservices.miarmacraft.handlers.ModDataHandler;
import net.miarma.api.microservices.miarmacraft.handlers.PlayerDataHandler;
import net.miarma.api.microservices.miarmacraft.routing.middlewares.MMCAuthGuard;
import net.miarma.api.microservices.miarmacraft.services.PlayerService;
import net.miarma.api.microservices.minecraft.handlers.ModDataHandler;
import net.miarma.api.microservices.minecraft.handlers.PlayerDataHandler;
import net.miarma.api.microservices.minecraft.routing.middlewares.MMCAuthGuard;
import net.miarma.api.microservices.minecraft.services.PlayerService;
public class MMCDataRouter {
public static void mount(Router router, Vertx vertx, Pool pool) {

View File

@@ -1,4 +1,4 @@
package net.miarma.api.microservices.miarmacraft.routing;
package net.miarma.api.microservices.minecraft.routing;
import net.miarma.api.backlib.Constants;

View File

@@ -1,12 +1,12 @@
package net.miarma.api.microservices.miarmacraft.routing;
package net.miarma.api.microservices.minecraft.routing;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.microservices.miarmacraft.handlers.PlayerLogicHandler;
import net.miarma.api.microservices.miarmacraft.routing.middlewares.MMCAuthGuard;
import net.miarma.api.microservices.miarmacraft.services.PlayerService;
import net.miarma.api.microservices.minecraft.handlers.PlayerLogicHandler;
import net.miarma.api.microservices.minecraft.routing.middlewares.MMCAuthGuard;
import net.miarma.api.microservices.minecraft.services.PlayerService;
public class MMCLogicRouter {
public static void mount(Router router, Vertx vertx, Pool pool) {

View File

@@ -1,12 +1,12 @@
package net.miarma.api.microservices.miarmacraft.routing.middlewares;
package net.miarma.api.microservices.minecraft.routing.middlewares;
import java.util.function.Consumer;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants.MMCUserRole;
import net.miarma.api.backlib.middlewares.AbstractAuthGuard;
import net.miarma.api.microservices.miarmacraft.entities.PlayerEntity;
import net.miarma.api.microservices.miarmacraft.services.PlayerService;
import net.miarma.api.microservices.minecraft.entities.PlayerEntity;
import net.miarma.api.microservices.minecraft.services.PlayerService;
public class MMCAuthGuard extends AbstractAuthGuard<PlayerEntity, MMCUserRole> {
private final PlayerService playerService;

Some files were not shown because too many files have changed in this diff Show More