bump: backlib and all microservices to v2.0, add: decoupled auth from identity using new Credential Entity model, still ongoing changes...

This commit is contained in:
Jose
2025-12-21 06:03:45 +01:00
parent 18c2f0f00b
commit 5136a67fba
105 changed files with 1506 additions and 1405 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() {
@@ -138,9 +144,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,148 +0,0 @@
package net.miarma.api.backlib.core.dao;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.sqlclient.Pool;
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;
import net.miarma.api.backlib.core.entities.FileEntity;
import java.util.List;
import java.util.Map;
public class FileDAO implements DataAccessObject<FileEntity, Integer> {
private final DatabaseManager db;
public FileDAO(Pool pool) {
this.db = DatabaseManager.getInstance(pool);
}
@Override
public Future<List<FileEntity>> getAll() {
return getAll(new QueryParams(Map.of(), new QueryFilters()));
}
@Override
public Future<FileEntity> getById(Integer id) {
Promise<FileEntity> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
.where(Map.of("file_id", id.toString()))
.build();
db.executeOne(query, FileEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
public Future<List<FileEntity>> getAll(QueryParams params) {
Promise<List<FileEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
.where(params.getFilters())
.orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder())
.limit(params.getQueryFilters().getLimit())
.offset(params.getQueryFilters().getOffset())
.build();
db.execute(query, FileEntity.class,
list -> promise.complete(list.isEmpty() ? List.of() : list),
promise::fail
);
return promise.future();
}
public Future<List<FileEntity>> getUserFiles(Integer userId) {
Promise<List<FileEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
.where(Map.of("uploaded_by", userId.toString()))
.build();
db.execute(query, FileEntity.class,
list -> promise.complete(list.isEmpty() ? List.of() : list),
promise::fail
);
return promise.future();
}
@Override
public Future<FileEntity> insert(FileEntity file) {
Promise<FileEntity> promise = Promise.promise();
String query = QueryBuilder.insert(file).build();
db.executeOne(query, FileEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<FileEntity> upsert(FileEntity file, String... conflictKeys) {
Promise<FileEntity> promise = Promise.promise();
String query = QueryBuilder.upsert(file, conflictKeys).build();
db.executeOne(query, FileEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<FileEntity> update(FileEntity file) {
Promise<FileEntity> promise = Promise.promise();
String query = QueryBuilder.update(file).build();
db.executeOne(query, FileEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> exists(Integer id) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(FileEntity.class)
.where(Map.of("file_id", id.toString()))
.build();
db.executeOne(query, FileEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> delete(Integer id) {
Promise<Boolean> promise = Promise.promise();
FileEntity file = new FileEntity();
file.setFile_id(id);
String query = QueryBuilder.delete(file).build();
db.executeOne(query, FileEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
}

View File

@@ -1,163 +0,0 @@
package net.miarma.api.backlib.core.dao;
import java.util.List;
import java.util.Map;
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.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> {
private final DatabaseManager db;
public UserDAO(Pool pool) {
this.db = DatabaseManager.getInstance(pool);
}
@Override
public Future<List<UserEntity>> getAll() {
return getAll(new QueryParams(Map.of(), new QueryFilters()));
}
@Override
public Future<UserEntity> getById(Integer id) {
Promise<UserEntity> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(Map.of("user_id", id.toString()))
.build();
db.executeOne(query, UserEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
public Future<List<UserEntity>> getAll(QueryParams params) {
Promise<List<UserEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(params.getFilters())
.orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder())
.limit(params.getQueryFilters().getLimit())
.offset(params.getQueryFilters().getOffset())
.build();
db.execute(query, UserEntity.class,
list -> promise.complete(list.isEmpty() ? List.of() : list),
promise::fail
);
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();
String query = QueryBuilder.insert(user).build();
db.executeOne(query, UserEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<UserEntity> upsert(UserEntity userEntity, String... conflictKeys) {
Promise<UserEntity> promise = Promise.promise();
String query = QueryBuilder.upsert(userEntity, conflictKeys).build();
db.executeOne(query, UserEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<UserEntity> update(UserEntity user) {
Promise<UserEntity> promise = Promise.promise();
String query = QueryBuilder.update(user).build();
db.executeOne(query, UserEntity.class,
_ -> promise.complete(user),
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> delete(Integer id) {
Promise<Boolean> promise = Promise.promise();
UserEntity user = new UserEntity();
user.setUser_id(id);
String query = QueryBuilder.delete(user).build();
db.executeOne(query, UserEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> exists(Integer id) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(Map.of("user_id", id.toString()))
.build();
db.executeOne(query, UserEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
}

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,101 +0,0 @@
package net.miarma.api.backlib.core.handlers;
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.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.util.JsonUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@SuppressWarnings("unused")
public class FileDataHandler {
private final FileService fileService;
public FileDataHandler(Pool pool) {
this.fileService = new FileService(pool);
}
public void getAll(RoutingContext ctx) {
QueryParams params = QueryParams.from(ctx);
fileService.getAll(params)
.onSuccess(files -> JsonUtil.sendJson(ctx, ApiStatus.OK, files))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void getById(RoutingContext ctx) {
Integer fileId = Integer.parseInt(ctx.pathParam("file_id"));
fileService.getById(fileId)
.onSuccess(file -> JsonUtil.sendJson(ctx, ApiStatus.OK, file))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void create(RoutingContext ctx) {
try {
String fileName = ctx.request().getFormAttribute("file_name");
String mimeType = ctx.request().getFormAttribute("mime_type");
int uploadedBy = Integer.parseInt(ctx.request().getFormAttribute("uploaded_by"));
int contextValue = Integer.parseInt(ctx.request().getFormAttribute("context"));
FileUpload upload = ctx.fileUploads().stream()
.filter(f -> f.name().equals("file"))
.findFirst()
.orElseThrow(() -> new RuntimeException("Archivo no encontrado"));
Buffer buffer = ctx.vertx().fileSystem().readFileBlocking(upload.uploadedFileName());
byte[] fileBinary = buffer.getBytes();
FileEntity file = new FileEntity();
file.setFile_name(fileName);
file.setMime_type(mimeType);
file.setUploaded_by(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);
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);
fileService.update(file)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.OK, result))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void delete(RoutingContext ctx) {
Integer fileId = Integer.parseInt(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);
JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
return;
}
fileService.delete(fileId)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
}

View File

@@ -1,67 +0,0 @@
package net.miarma.api.backlib.core.handlers;
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.JsonUtil;
public class FileLogicHandler {
private final Vertx vertx;
public FileLogicHandler(Vertx vertx) {
this.vertx = vertx;
}
private boolean validateAuth(RoutingContext ctx, JsonObject request) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Unauthorized");
return false;
}
String token = authHeader.substring(7);
int userId = JWTManager.getInstance().getUserId(token);
if (userId <= 0) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token");
return false;
}
request.put("userId", userId);
return true;
}
public void getUserFiles(RoutingContext ctx) {
JsonObject request = new JsonObject().put("action", "getUserFiles");
if (!validateAuth(ctx, request)) return;
vertx.eventBus().request(Constants.CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
JsonUtil.sendJson(ctx, ApiStatus.NOT_FOUND, null, "The user has no files");
}
});
}
public void downloadFile(RoutingContext ctx) {
JsonObject request = new JsonObject()
.put("action", "downloadFile")
.put("fileId", Integer.parseInt(ctx.pathParam("file_id")));
if (!validateAuth(ctx, request)) return;
vertx.eventBus().request(Constants.CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
JsonUtil.sendJson(ctx, ApiStatus.NOT_FOUND, null, "Error downloading file");
}
});
}
}

View File

@@ -1,44 +0,0 @@
package net.miarma.api.backlib.core.handlers;
import io.vertx.core.Vertx;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.util.JsonUtil;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class ScreenshotHandler {
private final WebClient webClient;
public ScreenshotHandler(Vertx vertx) {
this.webClient = WebClient.create(vertx);
}
public void getScreenshot(RoutingContext ctx) {
String url = ctx.request().getParam("url");
if (url == null || url.isEmpty()) {
ctx.response().setStatusCode(400).end("URL parameter is required");
return;
}
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
String microserviceUrl = "http://screenshoter:7000/screenshot?url=" + encodedUrl;
webClient.getAbs(microserviceUrl)
.send(ar -> {
if (ar.succeeded()) {
ctx.response()
.putHeader("Content-Type", "image/png")
.end(ar.result().body());
} else {
JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, "Could not generate the screenshot");
}
});
}
}

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,124 +0,0 @@
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;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class FileService {
private final FileDAO fileDAO;
private final FileValidator fileValidator;
public FileService(Pool pool) {
this.fileDAO = new FileDAO(pool);
this.fileValidator = new FileValidator();
}
public Future<List<FileEntity>> getAll(QueryParams params) {
return fileDAO.getAll(params);
}
public Future<FileEntity> getById(Integer id) {
return fileDAO.getById(id).compose(file -> {
if (file == null) {
return Future.failedFuture(new NotFoundException("File not found with id: " + id));
}
return Future.succeededFuture(file);
});
}
public Future<List<FileEntity>> getUserFiles(Integer userId) {
return fileDAO.getUserFiles(userId);
}
public Future<FileEntity> create(FileEntity file, byte[] fileBinary) {
return fileValidator.validate(file, fileBinary.length).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.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 ?
pathString.replace("\\", "\\\\") : pathString);
try {
Files.write(filePath, fileBinary);
} catch (IOException e) {
Constants.LOGGER.error("Error writing file to disk: ", e);
return Future.failedFuture(e);
}
return fileDAO.insert(file);
});
}
public Future<FileEntity> downloadFile(Integer fileId) {
return getById(fileId);
}
public Future<FileEntity> update(FileEntity file) {
return fileValidator.validate(file).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
return fileDAO.update(file);
});
}
public Future<FileEntity> upsert(FileEntity file) {
return fileValidator.validate(file).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
return fileDAO.upsert(file, "file_id");
});
}
public Future<Boolean> delete(Integer fileId) {
return getById(fileId).compose(file -> {
String dir = ConfigManager.getInstance()
.getFilesDir(file.getContext().toCtxString());
String filePath = dir + file.getFile_name();
Path path = Paths.get(filePath);
try {
Files.deleteIfExists(path);
} catch (IOException e) {
Constants.LOGGER.error("Error deleting file from disk: ", e);
return Future.failedFuture(e);
}
return fileDAO.delete(fileId).compose(deleted -> {
if (!deleted) {
return Future.failedFuture(new NotFoundException("File not found with id: " + fileId));
}
return Future.succeededFuture(true);
});
});
}
public Future<Boolean> exists(Integer fileId) {
return fileDAO.exists(fileId);
}
}

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,12 +1,14 @@
package net.miarma.api.backlib.db;
import java.lang.reflect.Field;
import org.slf4j.Logger;
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 java.lang.reflect.Field;
import net.miarma.api.backlib.interfaces.IValuableEnum;
import net.miarma.api.backlib.log.LoggerProvider;
/**
* Clase base para todas las entidades persistentes del sistema.
@@ -24,6 +26,7 @@ import java.lang.reflect.Field;
* @author José Manuel Amador Gallardo
*/
public abstract class AbstractEntity {
private final Logger LOGGER = LoggerProvider.getLogger();
/**
* Constructor por defecto. Requerido para instanciación sin datos.
@@ -86,7 +89,7 @@ public abstract class AbstractEntity {
}
}
default -> {
Constants.LOGGER.error("Type not supported yet: {} for field {}", type.getName(), name);
LOGGER.error("Type not supported yet: {} for field {}", type.getName(), name);
yield null;
}
};
@@ -95,7 +98,7 @@ public abstract class AbstractEntity {
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());
}
}
}
@@ -103,7 +106,7 @@ public abstract class AbstractEntity {
/**
* 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>
* <p>Si un campo implementa {@link IValuableEnum}, se usará su valor en lugar del nombre del enum.</p>
*
* @return Representación JSON de esta entidad.
*/
@@ -119,13 +122,13 @@ public abstract class AbstractEntity {
try {
Object value = field.get(this);
if (value instanceof ValuableEnum ve) {
if (value instanceof IValuableEnum ve) {
json.put(field.getName(), ve.getValue());
} 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();
@@ -150,7 +153,7 @@ 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("]");

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,14 +1,24 @@
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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.log.LoggerProvider;
/**
* Clase utilitaria para construir queries SQL dinámicamente mediante reflexión,
* usando entidades anotadas con {@link Table}.
@@ -21,6 +31,7 @@ import java.util.stream.Collectors;
* @author José Manuel Amador Gallardo
*/
public class QueryBuilder {
private final static Logger LOGGER = LoggerProvider.getLogger();
private final StringBuilder query;
private String sort;
private String order;
@@ -141,7 +152,7 @@ public class QueryBuilder {
String value = entry.getValue();
if (!validFields.contains(key)) {
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
continue;
}
@@ -186,7 +197,7 @@ public class QueryBuilder {
if (fieldValue != null) {
String key = field.getName();
if (!validFields.contains(key)) {
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
continue;
}
Object value = extractValue(fieldValue);
@@ -197,7 +208,7 @@ public class QueryBuilder {
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
this.query.append(joiner).append(" ");
@@ -239,7 +250,7 @@ public class QueryBuilder {
values.add("NULL");
}
} catch (IllegalArgumentException | IllegalAccessException e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
qb.query.append(columns).append(") ");
@@ -289,7 +300,7 @@ public class QueryBuilder {
setJoiner.add(fieldName + " = " + (value instanceof String
|| value instanceof LocalDateTime ? "'" + value + "'" : value));
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
@@ -343,7 +354,7 @@ public class QueryBuilder {
setJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value));
}
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
@@ -394,7 +405,7 @@ public class QueryBuilder {
}
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
@@ -436,7 +447,7 @@ public class QueryBuilder {
|| value instanceof LocalDateTime ? "'" + value + "'" : value.toString()));
}
} catch (Exception e) {
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
}
}
@@ -460,7 +471,7 @@ public class QueryBuilder {
.anyMatch(f -> f.equals(c));
if (!isValid) {
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en ORDER BY: {}", c);
LOGGER.warn("[QueryBuilder] Ignorando campo invalido en ORDER BY: {}", c);
return;
}
}

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,83 +1,87 @@
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();
private final ConfigManager config = ConfigManager.getInstance();
private final Algorithm algorithm;
private final JWTVerifier verifier;
private static JWTManager instance;
private JWTManager() {
this.algorithm = Algorithm.HMAC256(config.getStringProperty("jwt.secret"));
this.verifier = JWT.require(algorithm).build();
}
this.algorithm = Algorithm.HMAC256(config.getStringProperty("jwt.secret"));
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();
}
return instance;
}
/**
* 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.
*/
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"));
return JWT.create()
.withSubject(user_name)
.withClaim("userId", user_id)
.withClaim("role", role.name())
.withClaim("isAdmin", role == Constants.CoreUserRole.ADMIN)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME_MS))
.sign(algorithm);
if (instance == null) {
instance = new JWTManager();
}
return instance;
}
/**
* 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.
* Genera un token JWT usando UUID para el usuario.
*/
public DecodedJWT decodeWithoutVerification(String token) {
return JWT.decode(token);
public String generateToken(
String userName,
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(userName)
.withClaim("userId", userId.toString())
.withClaim("serviceId", serviceId)
.withClaim("role", role.name())
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + expiration))
.sign(algorithm);
}
public UUID extractUserId(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
String uuidStr = jwt.getClaim("userId").asString();
return UUID.fromString(uuidStr);
} catch (Exception e) {
return null;
}
}
public Integer extractServiceId(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("serviceId").asInt();
} catch (Exception e) {
return -1;
}
}
public String extractRole(String token) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("role").asString();
} catch (Exception e) {
return null;
}
}
/**
* 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);
@@ -86,77 +90,4 @@ public class JWTManager {
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) {
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();
} 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) {
try {
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("userId").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);
return jwt.getClaim("role").asString();
} catch (Exception e) {
return null;
}
}
}
}

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

@@ -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,14 +1,17 @@
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 -> {
long startTime = System.currentTimeMillis();
@@ -46,7 +49,7 @@ public class RouterUtil {
duration
);
Constants.LOGGER.info(log);
LOGGER.info(log);
});
ctx.next();