From af675a32b8eb033da67733cfb778645979add5a1 Mon Sep 17 00:00:00 2001 From: Jose Date: Sun, 21 Dec 2025 08:33:58 +0100 Subject: [PATCH] add: login working --- .../api/backlib/config/ConfigManager.java | 5 + .../miarma/api/backlib/db/AbstractEntity.java | 150 +++--- .../miarma/api/backlib/db/QueryBuilder.java | 503 +++++------------- .../api/backlib/security/JWTManager.java | 8 +- .../miarma/api/backlib/util/BufferUtil.java | 16 + bootstrap/pom.xml | 4 +- .../java/net/miarma/api/MasterVerticle.java | 17 +- .../src/main/resources/default.properties | 6 +- .../api/microservices/core/dao/UserDAO.java | 4 +- .../core/entities/CredentialEntity.java | 12 +- .../core/entities/UserEntity.java | 22 +- .../core/handlers/AuthHandler.java | 146 +++++ .../core/handlers/UserDataHandler.java | 64 +-- .../core/handlers/UserLogicHandler.java | 162 +----- .../core/routing/CoreDataRouter.java | 44 +- .../core/routing/CoreEndpoints.java | 67 ++- .../core/routing/CoreLogicRouter.java | 56 +- .../routing/middlewares/CoreAuthGuard.java | 15 +- .../core/services/AuthService.java | 4 +- .../core/services/UserService.java | 4 +- .../core/verticles/CoreDataVerticle.java | 227 +++----- .../core/verticles/CoreLogicVerticle.java | 2 +- .../core/verticles/CoreMainVerticle.java | 11 +- pom.xml | 8 +- 24 files changed, 628 insertions(+), 929 deletions(-) create mode 100644 backlib/src/main/java/net/miarma/api/backlib/util/BufferUtil.java create mode 100644 microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/AuthHandler.java diff --git a/backlib/src/main/java/net/miarma/api/backlib/config/ConfigManager.java b/backlib/src/main/java/net/miarma/api/backlib/config/ConfigManager.java index 0f36fd9..79df56f 100644 --- a/backlib/src/main/java/net/miarma/api/backlib/config/ConfigManager.java +++ b/backlib/src/main/java/net/miarma/api/backlib/config/ConfigManager.java @@ -124,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); } diff --git a/backlib/src/main/java/net/miarma/api/backlib/db/AbstractEntity.java b/backlib/src/main/java/net/miarma/api/backlib/db/AbstractEntity.java index 4db8b79..bb52627 100644 --- a/backlib/src/main/java/net/miarma/api/backlib/db/AbstractEntity.java +++ b/backlib/src/main/java/net/miarma/api/backlib/db/AbstractEntity.java @@ -1,67 +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.annotations.APIDontReturn; import net.miarma.api.backlib.interfaces.IValuableEnum; import net.miarma.api.backlib.log.LoggerProvider; +import net.miarma.api.backlib.util.BufferUtil; -/** - * Clase base para todas las entidades persistentes del sistema. - *

- * Proporciona utilidades para: - *

- * - * 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(); + private final Logger LOGGER = LoggerProvider.getLogger(); - /** - * Constructor por defecto. Requerido para instanciación sin datos. - */ - public AbstractEntity() {} + 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). - *

- * 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); @@ -73,43 +51,44 @@ public abstract class AbstractEntity { value = null; } } 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) { - yield null; - } - } - default -> { - LOGGER.error("Type not supported yet: {} for field {}", type.getName(), name); - yield null; - } - }; - + value = switch (type.getSimpleName()) { + 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 -> { + LOGGER.error("Type not supported yet: {} for field {}", type.getName(), field.getName()); + yield null; + } + }; } - field.set(this, value); + if (value != null) { + field.set(this, value); + } } catch (Exception e) { LOGGER.error("Error populating field {}: {}", field.getName(), e.getMessage()); } } } - /** - * Codifica esta entidad como un objeto JSON, omitiendo los campos anotados con {@link APIDontReturn}. - * - *

Si un campo implementa {@link IValuableEnum}, se usará su valor en lugar del nombre del enum.

- * - * @return Representación JSON de esta entidad. - */ public String encode() { JsonObject json = new JsonObject(); Class clazz = this.getClass(); @@ -117,13 +96,13 @@ 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 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); } @@ -133,31 +112,22 @@ public abstract class AbstractEntity { } clazz = clazz.getSuperclass(); } - return json.encode(); } - /** - * Devuelve una representación en texto de la entidad, mostrando todos los campos y sus valores. - * - *

Útil para logs y debugging.

- * - * @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(" [ "); - Field[] fields = this.getClass().getDeclaredFields(); - for (Field field : fields) { - field.setAccessible(true); - try { - sb.append(field.getName()).append("= ").append(field.get(this)).append(", "); - } catch (IllegalAccessException e) { - LOGGER.error("Error stringing field {}: {}", field.getName(), e.getMessage()); + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getSimpleName()).append(" [ "); + Field[] fields = this.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + try { + sb.append(field.getName()).append("=").append(field.get(this)).append(", "); + } catch (IllegalAccessException e) { + LOGGER.error("Error stringing field {}: {}", field.getName(), e.getMessage()); } - } - sb.append("]"); - return sb.toString(); + } + sb.append("]"); + return sb.toString(); } - -} +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/db/QueryBuilder.java b/backlib/src/main/java/net/miarma/api/backlib/db/QueryBuilder.java index 2dce697..c400acb 100644 --- a/backlib/src/main/java/net/miarma/api/backlib/db/QueryBuilder.java +++ b/backlib/src/main/java/net/miarma/api/backlib/db/QueryBuilder.java @@ -3,73 +3,40 @@ package net.miarma.api.backlib.db; import java.lang.reflect.Field; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -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.*; import java.util.stream.Collectors; import org.slf4j.Logger; - +import com.google.gson.annotations.SerializedName; 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}. - *

- * 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. - *

- * ¡Ojo! No ejecuta la query, solo la construye. - * - * @author José Manuel Amador Gallardo - */ public class QueryBuilder { - private final static Logger LOGGER = LoggerProvider.getLogger(); + + 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 String getTableName(Class clazz) { - if (clazz == null) { - throw new IllegalArgumentException("Class cannot be null"); - } - - if (clazz.isAnnotationPresent(Table.class)) { - Table annotation = clazz.getAnnotation(Table.class); - return annotation.value(); - } - throw new IllegalArgumentException("Class does not have @Table annotation"); + 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(); } - /** - * Devuelve la consulta SQL construida hasta el momento. - */ - public String getQuery() { - return query.toString(); + private static String getColumnName(Field field) { + SerializedName annotation = field.getAnnotation(SerializedName.class); + return annotation != null ? annotation.value() : field.getName(); } - /** - * 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"); @@ -78,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 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 QueryBuilder select(Class 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 filters) { - if (filters == null || filters.isEmpty()) { - return this; + public QueryBuilder where(Map filters) { + if (filters == null || filters.isEmpty() || entityClass == null) return this; + + Map javaToSql = new HashMap<>(); + Map sqlToSql = new HashMap<>(); + for (Field f : entityClass.getDeclaredFields()) { + String sqlCol = getColumnName(f); + javaToSql.put(f.getName(), sqlCol); + sqlToSql.put(sqlCol, sqlCol); } - Set validFields = entityClass != null - ? Arrays.stream(entityClass.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet()) - : Collections.emptySet(); - List conditions = new ArrayList<>(); - - for (Map.Entry entry : filters.entrySet()) { + for (Map.Entry entry : filters.entrySet()) { String key = entry.getKey(); - String value = entry.getValue(); - - if (!validFields.contains(key)) { + 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 (value.startsWith("(") && value.endsWith(")")) { - conditions.add(key + " IN " + value); - } else if (value.matches("-?\\d+(\\.\\d+)?")) { - conditions.add(key + " = " + value); - } else { - conditions.add(key + " = '" + value + "'"); + if (!conditions.isEmpty()) { + String prefix = query.toString().contains("WHERE") ? "AND " : "WHERE "; + query.append(prefix).append(String.join(" AND ", conditions)).append(" "); + } + return this; + } + + /** + * @param object Objeto con campos para WHERE. Soporta UUID y LocalDateTime + */ + public QueryBuilder where(T object) { + if (object == null) throw new IllegalArgumentException("Object cannot be null"); + if (entityClass == null) return this; + + Set validColumns = Arrays.stream(entityClass.getDeclaredFields()) + .map(QueryBuilder::getColumnName) + .collect(Collectors.toSet()); + + List conditions = new ArrayList<>(); + 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)); + } + } 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 QueryBuilder where(T object) { - if (object == null) { - throw new IllegalArgumentException("Object cannot be null"); - } - - Set 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)) { - 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) { - 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 el tipo del objeto a insertar - */ public static 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) { + 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 el tipo del objeto a actualizar - */ - public static QueryBuilder update(T object) { - if (object == null) { - throw new IllegalArgumentException("Object cannot be null"); - } + private static 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()); @@ -278,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) { + 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 el tipo del objeto a actualizar - */ - public static QueryBuilder updateWithNulls(T object) { - if (object == null) { - throw new IllegalArgumentException("Object cannot be null"); - } + public static 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 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) { - 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 el tipo del objeto a insertar o actualizar - */ public static QueryBuilder upsert(T object, String... conflictKeys) { if (object == null) throw new IllegalArgumentException("Object cannot be null"); @@ -382,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 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) { + 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 el tipo del objeto a eliminar - */ public static 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) { - LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage()); - } - } - - qb.query.append(joiner).append(" "); - return qb; + qb.entityClass = object.getClass(); + String table = getTableName(qb.entityClass); + qb.query.append("DELETE FROM ").append(table).append(" "); + return qb.where(object); } - /** - * 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 column, Optional order) { column.ifPresent(c -> { if (entityClass != null) { - boolean isValid = Arrays.stream(entityClass.getDeclaredFields()) - .map(Field::getName) - .anyMatch(f -> f.equals(c)); - - if (!isValid) { + boolean valid = Arrays.stream(entityClass.getDeclaredFields()) + .map(QueryBuilder::getColumnName) + .anyMatch(f -> f.equals(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 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 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() + ";"; } -} \ No newline at end of file +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/security/JWTManager.java b/backlib/src/main/java/net/miarma/api/backlib/security/JWTManager.java index c86f85b..1094ca5 100644 --- a/backlib/src/main/java/net/miarma/api/backlib/security/JWTManager.java +++ b/backlib/src/main/java/net/miarma/api/backlib/security/JWTManager.java @@ -32,7 +32,7 @@ public class JWTManager { * Genera un token JWT usando UUID para el usuario. */ public String generateToken( - String userName, + String displayName, UUID userId, IUserRole role, Integer serviceId, @@ -45,7 +45,7 @@ public class JWTManager { ); return JWT.create() - .withSubject(userName) + .withSubject(displayName) .withClaim("userId", userId.toString()) .withClaim("serviceId", serviceId) .withClaim("role", role.name()) @@ -54,6 +54,10 @@ public class JWTManager { .sign(algorithm); } + public DecodedJWT decode(String token) { + return JWT.decode(token); + } + public UUID extractUserId(String token) { try { DecodedJWT jwt = verifier.verify(token); diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/BufferUtil.java b/backlib/src/main/java/net/miarma/api/backlib/util/BufferUtil.java new file mode 100644 index 0000000..3f631eb --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/BufferUtil.java @@ -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); + } +} diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 0a33e7f..84b1e44 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -39,7 +39,8 @@ core ${project.version} - + diff --git a/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java b/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java index c782e0f..532d6d2 100644 --- a/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java +++ b/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java @@ -1,5 +1,7 @@ package net.miarma.api; +import java.util.List; + import org.slf4j.Logger; import io.vertx.core.AbstractVerticle; @@ -9,10 +11,10 @@ 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.minecraft.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 { @@ -38,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 huertos = vertx.deployVerticle(new HuertosMainVerticle()) + /*Future huertos = vertx.deployVerticle(new HuertosMainVerticle()) .onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(HuertosMainVerticle.class))) .onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(HuertosMainVerticle.class, err))); @@ -52,9 +54,10 @@ public class MasterVerticle extends AbstractVerticle { Future 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); diff --git a/bootstrap/src/main/resources/default.properties b/bootstrap/src/main/resources/default.properties index 4037d3c..5d47e5a 100644 --- a/bootstrap/src/main/resources/default.properties +++ b/bootstrap/src/main/resources/default.properties @@ -31,8 +31,8 @@ dp.poolSize=5 # HTTP Server Configuration inet.host=localhost -sso.logic.port=8080 -sso.data.port=8081 +core.logic.port=8080 +core.data.port=8081 minecraft.logic.port=8100 minecraft.data.port=8101 huertos.logic.port=8120 @@ -60,4 +60,4 @@ smtp.password.admin= smtp.password.noreply= # Discord Configuration -discord.webhook= \ No newline at end of file +discord.webhook= diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/dao/UserDAO.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/dao/UserDAO.java index 22d90bb..a624a79 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/dao/UserDAO.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/dao/UserDAO.java @@ -32,9 +32,11 @@ public class UserDAO implements DataAccessObject { Promise 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 diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/CredentialEntity.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/CredentialEntity.java index 8ee297c..aae903a 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/CredentialEntity.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/CredentialEntity.java @@ -1,14 +1,17 @@ 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 java.util.UUID; -import java.time.LocalDateTime; +import net.miarma.api.backlib.db.AbstractEntity; @Table("credentials") -public class CredentialEntity { +public class CredentialEntity extends AbstractEntity { @SerializedName("credential_id") private UUID credentialId; @@ -34,7 +37,8 @@ public class CredentialEntity { @SerializedName("updated_at") private LocalDateTime updatedAt; - public CredentialEntity() {} + public CredentialEntity() { } + public CredentialEntity(Row row) { super(row); } public UUID getCredentialId() { return credentialId; diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/UserEntity.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/UserEntity.java index b4ea1fd..bd2536e 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/UserEntity.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/entities/UserEntity.java @@ -3,6 +3,8 @@ 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; @@ -11,14 +13,24 @@ import net.miarma.api.microservices.core.enums.CoreUserGlobalStatus; @Table("users") public class UserEntity extends AbstractEntity { + @SerializedName("user_id") private UUID userId; - private String userName; - private String email; + + @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() { } @@ -27,12 +39,6 @@ public class UserEntity extends AbstractEntity { public UUID getUserId() { return userId; } public void setUserId(UUID userId) { this.userId = userId; } - 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 getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/AuthHandler.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/AuthHandler.java new file mode 100644 index 0000000..0915ad3 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/AuthHandler.java @@ -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)); + } +} \ No newline at end of file diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserDataHandler.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserDataHandler.java index 34b6c59..daa0829 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserDataHandler.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserDataHandler.java @@ -1,10 +1,10 @@ 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.Constants; import net.miarma.api.backlib.gson.GsonProvider; import net.miarma.api.backlib.http.ApiStatus; import net.miarma.api.backlib.http.QueryParams; @@ -15,7 +15,7 @@ import net.miarma.api.microservices.core.services.UserService; @SuppressWarnings("unused") public class UserDataHandler { - private final Gson GSON = GsonProvider.get(); + private final Gson GSON = GsonProvider.get(); private final UserService userService; public UserDataHandler(Pool pool) { @@ -23,52 +23,32 @@ public class UserDataHandler { } 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()); - }); + 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) { - 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 = 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()); - }); + 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) { - UserEntity user = 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()); - }); + 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) { - 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()); - }); + 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")); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserLogicHandler.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserLogicHandler.java index b2b7887..c300b32 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserLogicHandler.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/handlers/UserLogicHandler.java @@ -1,162 +1,26 @@ package net.miarma.api.microservices.core.handlers; -import com.auth0.jwt.interfaces.DecodedJWT; +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.EventBusUtil; import net.miarma.api.backlib.util.JsonUtil; -import net.miarma.api.microservices.core.entities.UserEntity; 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 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"); @@ -166,9 +30,9 @@ public class UserLogicHandler { } String token = authHeader.substring(7); - int userId = net.miarma.api.backlib.security.JWTManager.getInstance().getUserId(token); + UUID userId = JWTManager.getInstance().extractUserId(token); - if (userId <= 0) { + if (userId == null) { JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid token"); return; } @@ -177,7 +41,7 @@ public class UserLogicHandler { .put("action", "getInfo") .put("userId", userId); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); } else { @@ -193,7 +57,7 @@ public class UserLogicHandler { .put("action", "userExists") .put("userId", userId); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); } else { @@ -209,7 +73,7 @@ public class UserLogicHandler { .put("action", "getStatus") .put("userId", userId); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); } else { @@ -225,7 +89,7 @@ public class UserLogicHandler { .put("action", "getRole") .put("userId", userId); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); } else { @@ -241,7 +105,7 @@ public class UserLogicHandler { .put("action", "getAvatar") .put("userId", userId); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); } else { @@ -258,7 +122,7 @@ public class UserLogicHandler { .put("userId", body.getInteger("userId")) .put("status", body.getInteger("status")); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null); } else { @@ -275,7 +139,7 @@ public class UserLogicHandler { .put("userId", body.getInteger("userId")) .put("role", body.getInteger("role")); - vertx.eventBus().request(Constants.AUTH_EVENT_BUS, request, ar -> { + vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> { if (ar.succeeded()) { JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null); } else { diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreDataRouter.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreDataRouter.java index 6dc361a..1a53037 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreDataRouter.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreDataRouter.java @@ -4,32 +4,30 @@ 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.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.services.UserService; 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) { - UserDataHandler hUserData = new UserDataHandler(pool); - FileDataHandler hFileData = new FileDataHandler(pool); - UserService userService = new UserService(pool); - CoreAuthGuard authGuard = new CoreAuthGuard(userService); - - router.route().handler(BodyHandler.create()); + public static void mount(Router router, Vertx vertx, Pool pool) { + UserDataHandler hUserData = new UserDataHandler(pool); + FileDataHandler hFileData = new FileDataHandler(pool); + UserService userService = new UserService(pool); + CoreAuthGuard authGuard = new CoreAuthGuard(userService); + + 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.FILES).handler(authGuard.check()).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); - - } -} + 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(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); + } +} \ No newline at end of file diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreEndpoints.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreEndpoints.java index d8e5ba2..7ccc6cb 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreEndpoints.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreEndpoints.java @@ -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 - */ - - // 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 - - // 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 - - /* - * 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 -} + + private static final ConfigManager config = ConfigManager.getInstance(); + private static final String CORE = config.getApiPrefix("core"); + private static final String AUTH = config.getApiPrefix("auth"); + + /* 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"; + + /* 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"; + + 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"; +} \ No newline at end of file diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreLogicRouter.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreLogicRouter.java index 27c3658..8d61dd3 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreLogicRouter.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreLogicRouter.java @@ -4,41 +4,45 @@ 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.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.services.UserService; 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); - ScreenshotHandler hScreenshot = new ScreenshotHandler(vertx); + 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.get(CoreEndpoints.USER_ROLE).handler(authGuard.check()).handler(hUserLogic::getRole); - router.put(CoreEndpoints.USER_ROLE).handler(authGuard.check(CoreUserRole.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); - router.get(CoreEndpoints.USER_FILES).handler(authGuard.check()).handler(hFileLogic::getUserFiles); - - router.get(CoreEndpoints.SCREENSHOT).handler(hScreenshot::getScreenshot); - } -} + router.get(CoreEndpoints.USER_STATUS).handler(authGuard.check()).handler(hUserLogic::getStatus); + 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(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); + router.get(CoreEndpoints.USER_FILES).handler(authGuard.check()).handler(hFileLogic::getUserFiles); + + router.get(CoreEndpoints.SCREENSHOT).handler(hScreenshot::getScreenshot); + } +} \ No newline at end of file diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/middlewares/CoreAuthGuard.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/middlewares/CoreAuthGuard.java index 5cef285..e62d566 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/middlewares/CoreAuthGuard.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/middlewares/CoreAuthGuard.java @@ -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.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 { +public class CoreAuthGuard extends AbstractAuthGuard { private final UserService userService; public CoreAuthGuard(UserService userService) { @@ -16,12 +17,12 @@ public class CoreAuthGuard extends AbstractAuthGuard { } @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 callback) { + protected void getUserEntity(UUID userId, RoutingContext ctx, Consumer 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 { } @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; } } diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/services/AuthService.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/services/AuthService.java index bbc217a..2a7ed9b 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/services/AuthService.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/services/AuthService.java @@ -35,13 +35,13 @@ public class AuthService { public Future login(String login, String plainPassword, boolean keepLoggedIn) { return credentialDAO.getByServiceAndUsername(this.serviceId, login).compose(cred -> { if (cred == null) { - return Future.failedFuture(new BadRequestException("Invalid credentials")); + 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")); diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/services/UserService.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/services/UserService.java index 0861c25..5e7f083 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/services/UserService.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/services/UserService.java @@ -31,7 +31,7 @@ public class UserService { }); } - public Future updateProfile(UserEntity user) { + public Future update(UserEntity user) { return userDAO.update(user); } @@ -49,7 +49,7 @@ public class UserService { }); } - public Future deleteUser(UUID id) { + public Future delete(UUID id) { return userDAO.delete(id); } } \ No newline at end of file diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreDataVerticle.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreDataVerticle.java index 810a6ca..da6d822 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreDataVerticle.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreDataVerticle.java @@ -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.Constants; -import net.miarma.api.backlib.Constants.CoreUserGlobalStatus; -import net.miarma.api.backlib.Constants.CoreUserRole; import net.miarma.api.backlib.config.ConfigManager; -import net.miarma.api.microservices.core.entities.UserEntity; -import net.miarma.api.microservices.core.services.FileService; -import net.miarma.api.microservices.core.services.UserService; 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 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"); - 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)); + try { + switch (action) { + case "login" -> { + authService.login( + body.getString("login"), + body.getString("password"), + body.getBoolean("keepLoggedIn", false) + ).onSuccess(message::reply).onFailure(EventBusUtil.fail(message)); + } + + case "register" -> { + UserEntity profile = new UserEntity(); + profile.setDisplayName(body.getString("displayName")); + + 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 "downloadFile" -> { + fileService.getById(UUID.fromString(body.getString("fileId"))) + .onSuccess(message::reply) + .onFailure(EventBusUtil.fail(message)); + } + + default -> message.fail(404, "Action not found: " + action); } - - 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")); - - userService.register(user) - .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) - .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)); - } - - case "userExists" -> { - Integer userId = body.getInteger("userId"); - - userService.getById(userId) - .onSuccess(user -> { - Map 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 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 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 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()); } }); } diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreLogicVerticle.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreLogicVerticle.java index 2e2da70..5aeb45e 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreLogicVerticle.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreLogicVerticle.java @@ -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()); }); diff --git a/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreMainVerticle.java b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreMainVerticle.java index b7dd9bf..c42e6f0 100644 --- a/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreMainVerticle.java +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreMainVerticle.java @@ -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.Constants; 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); diff --git a/pom.xml b/pom.xml index 60af061..d5b33e9 100644 --- a/pom.xml +++ b/pom.xml @@ -11,10 +11,10 @@ backlib bootstrap - microservices/huertos + microservices/core @@ -29,7 +29,7 @@ core ${project.version} - +