diff --git a/backlib/.gitignore b/backlib/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/backlib/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/backlib/pom.xml b/backlib/pom.xml new file mode 100644 index 0000000..914ce6f --- /dev/null +++ b/backlib/pom.xml @@ -0,0 +1,164 @@ + + 4.0.0 + net.miarma.api + backlib + 1.2.0 + + + 23 + 23 + + + + + jitpack.io + https://jitpack.io + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + + io.vertx + vertx-core + 4.5.13 + + + + + io.vertx + vertx-web + 4.5.13 + + + + + io.vertx + vertx-web-client + 4.5.13 + + + + + io.vertx + vertx-mysql-client + 4.5.13 + + + + + io.vertx + vertx-mail-client + 4.5.16 + + + + + io.vertx + vertx-redis-client + 4.5.16 + + + + + com.google.code.gson + gson + 2.12.1 + + + + + org.mindrot + jbcrypt + 0.4 + + + + + com.auth0 + java-jwt + 4.5.0 + + + + + org.slf4j + slf4j-api + 2.0.12 + + + + ch.qos.logback + logback-classic + 1.5.13 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.18.3 + + + + + com.sun.mail + jakarta.mail + 2.0.1 + + + + + com.github.eduardomcb + discord-webhook + 1.0.0 + + + + + BackLib + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.backlib.MainVerticle + + + + + + + + + + diff --git a/backlib/src/main/java/net/miarma/api/backlib/ConfigManager.java b/backlib/src/main/java/net/miarma/api/backlib/ConfigManager.java new file mode 100644 index 0000000..3aab338 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/ConfigManager.java @@ -0,0 +1,146 @@ +package net.miarma.api.backlib; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +/** + * 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. + * Proporciona métodos para obtener la URL de la base de datos, directorios de archivos, + * y propiedades específicas como host, puerto, etc. + *

+ * Esta clase sigue el patron Singleton para asegurar una sola instancia. + * @author José Manuel Amador Gallardo + */ +public class ConfigManager { + private static ConfigManager instance; + private final File configFile; + private final Properties config; + private static final String CONFIG_FILE_NAME = "config.properties"; + + private ConfigManager() { + String path = getBaseDir() + CONFIG_FILE_NAME; + this.configFile = new File(path); + this.config = new Properties(); + } + + public static synchronized ConfigManager getInstance() { + if (instance == null) { + instance = new ConfigManager(); + } + return instance; + } + + public void loadConfig() { + try (FileInputStream fis = new FileInputStream(configFile); + InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) { + config.load(isr); + } catch (IOException e) { + Constants.LOGGER.error("Error loading configuration file: ", e); + } + } + public File getConfigFile() { + return configFile; + } + + public String getJdbcUrl() { + return String.format("%s://%s:%s/%s", + config.getProperty("db.protocol"), + config.getProperty("db.host"), + config.getProperty("db.port"), + config.getProperty("db.name")); + } + + public String getHost() { + return this.getStringProperty("inet.host"); + } + + public String getHomeDir() { + if (isDocker()) { + return "/data/"; + } + return getOS() == OSType.WINDOWS ? + "C:/Users/" + System.getProperty("user.name") + "/" : + System.getProperty("user.home").contains("root") ? "/root/" : + "/home/" + System.getProperty("user.name") + "/"; + } + + public String getBaseDir() { + if (isDocker()) { + return getHomeDir() + ".config/"; + } + return getHomeDir() + (getOS() == OSType.WINDOWS ? ".miarmacoreapi/" : + getOS() == OSType.LINUX ? ".config/miarmacoreapi/" : + ".contaminus/"); + } + + public String getFilesDir(String context) { + if (config.getProperty("files.dir") != null) { + return config.getProperty("files.dir"); + } + if (isDocker()) { + return "/files/" + context + "/"; + } + return getOS() == OSType.WINDOWS ? + System.getProperty("user.home") + "\\" + "Documents\\" + context + "\\" : + "/var/www/files/" + context + "/"; + } + + public String getModsDir() { + return getFilesDir("miarmacraft") + "mods/"; + } + + public String getWebRoot() { + if (config.getProperty("web.root") != null) { + return config.getProperty("web.root"); + } + return getBaseDir() + "webroot/"; + } + + + public static OSType getOS() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return OSType.WINDOWS; + } else if (os.contains("nix") || os.contains("nux")) { + return OSType.LINUX; + } else { + return OSType.INVALID_OS; + } + } + + public static boolean isDocker() { + return Boolean.parseBoolean(System.getenv("RUNNING_IN_DOCKER")); + } + + public String getStringProperty(String key) { + return config.getProperty(key); + } + + public int getIntProperty(String key) { + String value = config.getProperty(key); + return value != null ? Integer.parseInt(value) : 10; + } + + public boolean getBooleanProperty(String key) { + return Boolean.parseBoolean(config.getProperty(key)); + } + + public void setProperty(String key, String value) { + config.setProperty(key, value); + saveConfig(); + } + + private void saveConfig() { + try (FileOutputStream fos = new FileOutputStream(configFile)) { + config.store(fos, "Configuration for: " + Constants.APP_NAME); + } catch (IOException e) { + Constants.LOGGER.error("Error saving configuration file: ", e); + } + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/Constants.java b/backlib/src/main/java/net/miarma/api/backlib/Constants.java new file mode 100644 index 0000000..94ccf06 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/Constants.java @@ -0,0 +1,469 @@ +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 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."); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/LogAccumulator.java b/backlib/src/main/java/net/miarma/api/backlib/LogAccumulator.java new file mode 100644 index 0000000..a9eb4f6 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/LogAccumulator.java @@ -0,0 +1,25 @@ +package net.miarma.api.backlib; + +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class LogAccumulator { + private static final List LOGS = Collections.synchronizedList(new ArrayList<>()); + private static final AtomicInteger COUNTER = new AtomicInteger(0); + + public static void add(String message) { + LOGS.add(LogEntry.of(COUNTER.getAndIncrement(), message)); + } + + public static void flushToLogger(Logger logger) { + LOGS.stream() + .sorted(Comparator.comparingInt(LogEntry::order)) + .forEach(entry -> logger.info(entry.message())); + LOGS.clear(); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/LogEntry.java b/backlib/src/main/java/net/miarma/api/backlib/LogEntry.java new file mode 100644 index 0000000..a3fe2e3 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/LogEntry.java @@ -0,0 +1,7 @@ +package net.miarma.api.backlib; + +public record LogEntry(int order, String message) { + public static LogEntry of(int order, String message) { + return new LogEntry(order, message); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/OSType.java b/backlib/src/main/java/net/miarma/api/backlib/OSType.java new file mode 100644 index 0000000..ded6952 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/OSType.java @@ -0,0 +1,9 @@ +package net.miarma.api.backlib; + +/** + * Enum que representa los diferentes tipos de sistemas operativos soportados + * @author José Manuel Amador Gallardo + */ +public enum OSType { + LINUX, WINDOWS, INVALID_OS +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/ValuableEnum.java b/backlib/src/main/java/net/miarma/api/backlib/ValuableEnum.java new file mode 100644 index 0000000..4b1acdb --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/ValuableEnum.java @@ -0,0 +1,9 @@ +package net.miarma.api.backlib; + +/** + * Interfaz que define un enum con un valor entero asociado + * @author José Manuel Amador Gallardo + */ +public interface ValuableEnum { + int getValue(); +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/annotations/APIDontReturn.java b/backlib/src/main/java/net/miarma/api/backlib/annotations/APIDontReturn.java new file mode 100644 index 0000000..7335883 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/annotations/APIDontReturn.java @@ -0,0 +1,16 @@ +package net.miarma.api.backlib.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Esta anotación se utiliza para indicar que un campo no debe ser incluido en la respuesta de la API. + * Se aplica a campos de clases o interfaces y está disponible en tiempo de ejecución. + * + * @author José Manuel Amador Gallardo + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface APIDontReturn {} diff --git a/backlib/src/main/java/net/miarma/api/backlib/annotations/Table.java b/backlib/src/main/java/net/miarma/api/backlib/annotations/Table.java new file mode 100644 index 0000000..085c7b9 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/annotations/Table.java @@ -0,0 +1,18 @@ +package net.miarma.api.backlib.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Anotación para definir el nombre de una tabla en la base de datos. + * Se utiliza para mapear una clase a una tabla específica. + * + * @author José Manuel Amador Gallardo + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Table { + String value(); +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/dao/FileDAO.java b/backlib/src/main/java/net/miarma/api/backlib/core/dao/FileDAO.java new file mode 100644 index 0000000..739a232 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/dao/FileDAO.java @@ -0,0 +1,148 @@ +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 { + + private final DatabaseManager db; + + public FileDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise 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> getAll(QueryParams params) { + Promise> 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> getUserFiles(Integer userId) { + Promise> 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 insert(FileEntity file) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(file).build(); + + db.executeOne(query, FileEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(FileEntity file, String... conflictKeys) { + Promise 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 update(FileEntity file) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(file).build(); + + db.executeOne(query, FileEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise 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 delete(Integer id) { + Promise 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(); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/dao/UserDAO.java b/backlib/src/main/java/net/miarma/api/backlib/core/dao/UserDAO.java new file mode 100644 index 0000000..be6b6ea --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/dao/UserDAO.java @@ -0,0 +1,163 @@ +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 { + + private final DatabaseManager db; + + public UserDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise 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> getAll(QueryParams params) { + Promise> 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 getByEmail(String email) { + Promise 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 getByUserName(String userName) { + Promise 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 insert(UserEntity user) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(user).build(); + + db.executeOne(query, UserEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(UserEntity userEntity, String... conflictKeys) { + Promise 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 update(UserEntity user) { + Promise 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 delete(Integer id) { + Promise 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 exists(Integer id) { + Promise 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(); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/entities/FileEntity.java b/backlib/src/main/java/net/miarma/api/backlib/core/entities/FileEntity.java new file mode 100644 index 0000000..d2abe45 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/entities/FileEntity.java @@ -0,0 +1,83 @@ +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; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/entities/UserEntity.java b/backlib/src/main/java/net/miarma/api/backlib/core/entities/UserEntity.java new file mode 100644 index 0000000..33a7e0e --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/entities/UserEntity.java @@ -0,0 +1,63 @@ +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; + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/handlers/FileDataHandler.java b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/FileDataHandler.java new file mode 100644 index 0000000..332ff39 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/FileDataHandler.java @@ -0,0 +1,101 @@ +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())); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/handlers/FileLogicHandler.java b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/FileLogicHandler.java new file mode 100644 index 0000000..ab10c87 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/FileLogicHandler.java @@ -0,0 +1,67 @@ +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"); + } + }); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/handlers/ScreenshotHandler.java b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/ScreenshotHandler.java new file mode 100644 index 0000000..b238252 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/ScreenshotHandler.java @@ -0,0 +1,44 @@ +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"); + } + }); + + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/handlers/UserDataHandler.java b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/UserDataHandler.java new file mode 100644 index 0000000..c99512f --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/UserDataHandler.java @@ -0,0 +1,70 @@ +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()); + }); + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/handlers/UserLogicHandler.java b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/UserLogicHandler.java new file mode 100644 index 0000000..90e96f2 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/handlers/UserLogicHandler.java @@ -0,0 +1,286 @@ +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()); + } + }); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/services/FileService.java b/backlib/src/main/java/net/miarma/api/backlib/core/services/FileService.java new file mode 100644 index 0000000..1b2dde1 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/services/FileService.java @@ -0,0 +1,124 @@ +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> getAll(QueryParams params) { + return fileDAO.getAll(params); + } + + public Future 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> getUserFiles(Integer userId) { + return fileDAO.getUserFiles(userId); + } + + public Future 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 downloadFile(Integer fileId) { + return getById(fileId); + } + + public Future 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 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 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 exists(Integer fileId) { + return fileDAO.exists(fileId); + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/services/UserService.java b/backlib/src/main/java/net/miarma/api/backlib/core/services/UserService.java new file mode 100644 index 0000000..ab1407c --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/services/UserService.java @@ -0,0 +1,209 @@ +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 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 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 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 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 validateToken(String token) { + JWTManager jwtManager = JWTManager.getInstance(); + return jwtManager.isValid(token) ? + Future.succeededFuture(true) : + Future.failedFuture(new UnauthorizedException("Invalid token")); + } + + /* USERS OPERATIONS */ + + public Future> getAll(QueryParams params) { + return userDAO.getAll(params); + } + + public Future 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 getByEmail(String email) { + return userDAO.getByEmail(email); + } + + public Future getByUserName(String userName) { + return userDAO.getByUserName(userName); + } + + public Future 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 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 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 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 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 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); + }); + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/validators/FileValidator.java b/backlib/src/main/java/net/miarma/api/backlib/core/validators/FileValidator.java new file mode 100644 index 0000000..3190f95 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/validators/FileValidator.java @@ -0,0 +1,84 @@ +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 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 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); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/core/validators/UserValidator.java b/backlib/src/main/java/net/miarma/api/backlib/core/validators/UserValidator.java new file mode 100644 index 0000000..ff42903 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/core/validators/UserValidator.java @@ -0,0 +1,44 @@ +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 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); + } +} 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 new file mode 100644 index 0000000..b5bb1b3 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/db/AbstractEntity.java @@ -0,0 +1,160 @@ +package net.miarma.api.backlib.db; + +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; + +/** + * 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 { + + /** + * Constructor por defecto. Requerido para instanciación sin datos. + */ + public AbstractEntity() {} + + /** + * Constructor que inicializa los campos de la entidad a partir de una fila de base de datos. + * + * @param row Fila SQL proporcionada por Vert.x. + */ + public AbstractEntity(Row row) { + populateFromRow(row); + } + + /** + * Rellena los campos del objeto usando reflexión a partir de una {@link Row} de Vert.x. + * Se soportan tipos básicos (String, int, boolean, etc.), enums con método estático {@code fromInt(int)}, + * y {@link java.math.BigDecimal} (a través del tipo {@code Numeric} de Vert.x). + *

+ * 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 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(); + + Object value; + if (type.isEnum()) { + Integer intValue = row.getInteger(name); + if (intValue != null) { + try { + var method = type.getMethod("fromInt", int.class); + value = method.invoke(null, intValue); + } catch (Exception e) { + value = null; + } + } else { + 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 -> { + Constants.LOGGER.error("Type not supported yet: {} for field {}", type.getName(), name); + yield null; + } + }; + + } + + field.set(this, value); + } catch (Exception e) { + Constants.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 ValuableEnum}, 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(); + + while (clazz != null) { + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(APIDontReturn.class)) continue; + + field.setAccessible(true); + try { + Object value = field.get(this); + + if (value instanceof ValuableEnum ve) { + 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()); + } + } + 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) { + Constants.LOGGER.error("Error stringing field {}: {}", field.getName(), e.getMessage()); + } + } + sb.append("]"); + return sb.toString(); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/db/DataAccessObject.java b/backlib/src/main/java/net/miarma/api/backlib/db/DataAccessObject.java new file mode 100644 index 0000000..5930719 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/db/DataAccessObject.java @@ -0,0 +1,73 @@ +package net.miarma.api.backlib.db; + +import io.vertx.core.Future; + +import java.util.List; + +/** + * Interfaz genérica para operaciones CRUD básicas en una base de datos, + * adaptada al modelo asincrónico de Vert.x usando {@link Future}. + * + * @param Tipo de la entidad gestionada. + * @param Tipo del identificador único de la entidad. + * + * @author José Manuel Amador Gallardo + */ +public interface DataAccessObject { + + /** + * Recupera todos los registros de la entidad. + * + * @return Un {@link Future} que contiene una lista con todas las entidades encontradas. + */ + Future> getAll(); + + /** + * Recupera una entidad por su identificador. + * + * @param id Identificador de la entidad. + * @return Un {@link Future} que contiene la entidad, o falla si no se encuentra. + */ + Future getById(ID id); + + /** + * Inserta una nueva entidad en la base de datos. + * + * @param t Entidad a insertar. + * @return Un {@link Future} que contiene la entidad insertada, posiblemente con su ID asignado. + */ + Future insert(T t); + + /** + * Inserta o actualiza una entidad en la base de datos. + * Si la entidad ya existe, se actualiza; si no, se inserta como nueva. + * + * @param t Entidad a insertar o actualizar. + * @return Un {@link Future} que contiene la entidad insertada o actualizada. + */ + Future upsert(T t, String... conflictKeys); + + /** + * Actualiza una entidad existente. + * + * @param t Entidad con los datos actualizados. + * @return Un {@link Future} que contiene la entidad actualizada. + */ + Future update(T t); + + /** + * Elimina una entidad por su identificador. + * + * @param id Identificador de la entidad a eliminar. + * @return Un {@link Future} que indica si la operación fue exitosa. + */ + Future delete(ID id); + + /** + * Comprueba si existe una entidad con el identificador proporcionado. + * + * @param id Identificador a comprobar. + * @return Un {@link Future} que contiene {@code true} si existe, o {@code false} si no. + */ + Future exists(ID id); +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/db/DatabaseManager.java b/backlib/src/main/java/net/miarma/api/backlib/db/DatabaseManager.java new file mode 100644 index 0000000..400a48f --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/db/DatabaseManager.java @@ -0,0 +1,131 @@ +package net.miarma.api.backlib.db; + +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; + +/** + * Gestor centralizado de acceso a la base de datos utilizando Vert.x SQL Client. + * + *

+ * Esta clase sigue el patron Singleton para asegurar una sola instancia. + * + * + * @author José Manuel Amador Gallardo + */ +public class DatabaseManager { + + private static DatabaseManager instance; + private final Pool pool; + + /** + * Constructor privado para seguir el patrón Singleton. + * + * @param pool el pool de conexiones proporcionado por Vert.x + */ + private DatabaseManager(Pool pool) { + this.pool = pool; + } + + /** + * Devuelve la instancia única de {@link DatabaseManager}. Si no existe, la crea. + * + * @param pool el pool de conexiones a reutilizar + * @return la instancia singleton de DatabaseManager + */ + public static synchronized DatabaseManager getInstance(Pool pool) { + if (instance == null) { + instance = new DatabaseManager(pool); + } + return instance; + } + + /** + * Devuelve el pool de conexiones actual. + * + * @return el pool de conexiones + */ + public Pool getPool() { + return pool; + } + + /** + * Realiza una consulta simple para verificar que la conexión con la base de datos funciona. + * + * @return un {@link Future} que representa el resultado de la consulta + */ + public Future> testConnection() { + return pool.query("SELECT 1").execute(); + } + + /** + * Ejecuta una consulta SQL que devuelve múltiples resultados y los convierte en objetos de tipo {@code T}. + * + * @param query la consulta SQL a ejecutar + * @param clazz clase del objeto a instanciar desde cada fila del resultado + * @param onSuccess callback que se ejecuta si la consulta fue exitosa + * @param onFailure callback que se ejecuta si ocurre un error + * @param tipo del objeto a devolver + * @return un {@link Future} con la lista de resultados convertidos + */ + public Future> execute(String query, Class clazz, Handler> onSuccess, + Handler onFailure) { + return pool.query(query).execute().map(rows -> { + List results = new ArrayList<>(); + for (Row row : rows) { + try { + Constructor constructor = clazz.getConstructor(Row.class); + results.add(constructor.newInstance(row)); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException + | InvocationTargetException e) { + Constants.LOGGER.error("Error instantiating class: {}", e.getMessage()); + } + } + return results; + }).onComplete(ar -> { + if (ar.succeeded()) { + onSuccess.handle(ar.result()); + } else { + onFailure.handle(ar.cause()); + } + }); + } + + /** + * Ejecuta una consulta SQL que devuelve como máximo una fila y la convierte en un objeto de tipo {@code T}. + * + * @param query la consulta SQL a ejecutar + * @param clazz clase del objeto a instanciar desde la fila del resultado + * @param onSuccess callback que se ejecuta si la consulta fue exitosa + * @param onFailure callback que se ejecuta si ocurre un error + * @param tipo del objeto a devolver + * @return un {@link Future} con el objeto instanciado, o null si no hay resultados + */ + public Future executeOne(String query, Class clazz, Handler onSuccess, Handler onFailure) { + return pool.query(query).execute().map(rows -> { + for (Row row : rows) { + try { + Constructor constructor = clazz.getConstructor(Row.class); + return constructor.newInstance(row); + } catch (Exception e) { + Constants.LOGGER.error("Error instantiating class: {}", e.getMessage()); + } + } + return null; // Si no hay filas + }).onComplete(ar -> { + if (ar.succeeded()) { + onSuccess.handle(ar.result()); + } else { + onFailure.handle(ar.cause()); + } + }); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/db/DatabaseProvider.java b/backlib/src/main/java/net/miarma/api/backlib/db/DatabaseProvider.java new file mode 100644 index 0000000..d1dc777 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/db/DatabaseProvider.java @@ -0,0 +1,49 @@ +package net.miarma.api.backlib.db; + +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; + +/** + * Factoría de {@link Pool} para conexiones MySQL usando Vert.x. + * + *

+ * Se apoya en {@link ConfigManager} para extraer la configuración de la BBDD + * (host, puerto, nombre, usuario y contraseña) y crea un pool con un tamaño + * máximo de 10 conexiones. + *

+ * + * @author José Manuel Amador Gallardo + */ +public class DatabaseProvider { + + /** + * Crea y configura un pool de conexiones MySQL. + * + * @param vertx instancia principal de Vert.x + * @param config gestor de configuración con las propiedades necesarias: + *
    + *
  • db.port – puerto del servidor MySQL
  • + *
  • db.host – host o IP
  • + *
  • db.name – nombre de la base de datos
  • + *
  • db.user – usuario de la base
  • + *
  • db.password – contraseña del usuario
  • + *
+ * @return un {@link Pool} listo para usarse en consultas Vert.x + */ + public static Pool createPool(Vertx vertx, ConfigManager config) { + MySQLConnectOptions connectOptions = new MySQLConnectOptions() + .setPort(config.getIntProperty("db.port")) + .setHost(config.getStringProperty("db.host")) + .setDatabase(config.getStringProperty("db.name")) + .setUser(config.getStringProperty("db.user")) + .setPassword(config.getStringProperty("db.password")); + + PoolOptions poolOptions = new PoolOptions() + .setMaxSize(10); + + return Pool.pool(vertx, connectOptions, poolOptions); + } +} 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 new file mode 100644 index 0000000..4409868 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/db/QueryBuilder.java @@ -0,0 +1,516 @@ +package net.miarma.api.backlib.db; + +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.annotations.Table; + +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Clase utilitaria para construir queries SQL dinámicamente mediante reflexión, + * usando entidades anotadas con {@link Table}. + *

+ * 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 StringBuilder query; + private String sort; + private String order; + private String limit; + 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"); + } + + /** + * Devuelve la consulta SQL construida hasta el momento. + */ + public String getQuery() { + return query.toString(); + } + + /** + * Extrae el valor de un campo, manejando enums y tipos especiales. + * Si es un Enum y tiene getValue(), lo usa; si no, devuelve el name(). + * Si es un LocalDateTime, lo convierte a String en formato SQL. + */ + private static Object extractValue(Object fieldValue) { + if (fieldValue instanceof Enum) { + try { + var method = fieldValue.getClass().getMethod("getValue"); + return method.invoke(fieldValue); + } catch (Exception e) { + 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 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 + */ + 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(" "); + 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 + */ + public QueryBuilder where(Map filters) { + if (filters == null || filters.isEmpty()) { + return this; + } + + 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()) { + String key = entry.getKey(); + String value = entry.getValue(); + + if (!validFields.contains(key)) { + Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key); + continue; + } + + 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()) { + query.append("WHERE ").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)) { + Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key); + continue; + } + Object value = extractValue(fieldValue); + if (value instanceof String || value instanceof LocalDateTime) { + joiner.add(key + " = '" + value + "'"); + } else { + joiner.add(key + " = " + value.toString()); + } + } + } catch (IllegalArgumentException | IllegalAccessException e) { + Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage()); + } + } + this.query.append(joiner).append(" "); + return this; + } + + /** + * Construye una consulta INSERT para el objeto dado, insertando todos sus campos. + * Los valores se extraen mediante reflexión y se manejan adecuadamente. + * + * @param object el objeto a insertar + * @return el QueryBuilder actual para encadenar más métodos + * @param el tipo del objeto a insertar + */ + public static QueryBuilder insert(T object) { + 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(", "); + for (Field field : object.getClass().getDeclaredFields()) { + field.setAccessible(true); + try { + columns.add(field.getName()); + Object fieldValue = field.get(object); + if (fieldValue != null) { + Object value = extractValue(fieldValue); + if (value instanceof String || value instanceof LocalDateTime) { + values.add("'" + escapeSql((String) value) + "'"); + } else { + values.add(value.toString()); + } + } else { + values.add("NULL"); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage()); + } + } + qb.query.append(columns).append(") "); + qb.query.append("VALUES (").append(values).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"); + } + + QueryBuilder qb = new QueryBuilder(); + String table = getTableName(object.getClass()); + qb.query.append("UPDATE ").append(table).append(" SET "); + + 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")) { + idField = field; + whereJoiner.add(fieldName + " = " + (value instanceof String + || value instanceof LocalDateTime ? "'" + value + "'" : value)); + continue; + } + + setJoiner.add(fieldName + " = " + (value instanceof String + || value instanceof LocalDateTime ? "'" + value + "'" : value)); + } catch (Exception e) { + Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage()); + } + } + + if (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 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"); + } + + QueryBuilder qb = new QueryBuilder(); + String table = getTableName(object.getClass()); + qb.query.append("UPDATE ").append(table).append(" SET "); + + StringJoiner setJoiner = new StringJoiner(", "); + StringJoiner whereJoiner = new StringJoiner(" AND "); + + Field idField = null; + + for (Field field : object.getClass().getDeclaredFields()) { + field.setAccessible(true); + try { + String fieldName = field.getName(); + Object fieldValue = field.get(object); + + if (fieldName.endsWith("_id")) { + idField = field; + Object value = extractValue(fieldValue); + whereJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value)); + continue; + } + + if (fieldValue == null) { + setJoiner.add(fieldName + " = NULL"); + } else { + Object value = extractValue(fieldValue); + setJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value)); + } + } catch (Exception e) { + Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage()); + } + } + + if (idField == null) { + throw new IllegalArgumentException("No ID field (ending with _id) found for WHERE clause"); + } + + qb.query.append(setJoiner).append(" WHERE ").append(whereJoiner); + return qb; + } + + /** + * Construye una consulta UPSERT (INSERT o UPDATE) para el objeto dado. + * Si hay claves de conflicto, se actualizan los campos excepto las claves duplicadas. + * + * @param object el objeto a insertar o actualizar + * @param conflictKeys las claves que causan conflictos y no deben actualizarse + * @return el QueryBuilder actual para encadenar más métodos + * @param 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"); + + QueryBuilder qb = new QueryBuilder(); + String table = getTableName(object.getClass()); + qb.query.append("INSERT INTO ").append(table).append(" "); + + StringJoiner columns = new StringJoiner(", "); + StringJoiner values = 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) { + Constants.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(" ON DUPLICATE KEY UPDATE "); + StringJoiner updateSet = new StringJoiner(", "); + updates.forEach((k, v) -> updateSet.add(k + " = " + v)); + qb.query.append(updateSet); + } + + 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) { + Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage()); + } + } + + qb.query.append(joiner).append(" "); + return qb; + } + + /** + * Añade una cláusula ORDER BY a la consulta actual, ordenando por la columna y el orden especificados. + * Si la columna no es válida, se ignora. + * + * @param column la columna por la que ordenar + * @param order el orden (ASC o DESC); si no se especifica, se asume ASC + * @return el QueryBuilder actual para encadenar más métodos + */ + public QueryBuilder orderBy(Optional 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) { + Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en ORDER BY: {}", c); + return; + } + } + + sort = "ORDER BY " + c + " "; + order.ifPresent(o -> sort += o.equalsIgnoreCase("asc") ? "ASC" : "DESC" + " "); + }); + 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 + " "); + 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 + " "); + 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); + } + return query.toString().trim() + ";"; + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/AlreadyExistsException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/AlreadyExistsException.java new file mode 100644 index 0000000..0d0a864 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/AlreadyExistsException.java @@ -0,0 +1,35 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando se intenta crear o registrar un recurso + * que ya existe en el sistema. Por ejemplo, un usuario con un email duplicado + * o un identificador ya registrado. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class AlreadyExistsException extends RuntimeException { + + private static final long serialVersionUID = -6479166578011003074L; + + /** + * Crea una nueva instancia de {@code AlreadyExistsException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public AlreadyExistsException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code AlreadyExistsException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public AlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/BadGatewayException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/BadGatewayException.java new file mode 100644 index 0000000..b76cdb5 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/BadGatewayException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando un servidor actúa como puerta de enlace o proxy + * y recibe una respuesta inválida o no válida de un servidor ascendente. + * Esto puede ocurrir, por ejemplo, cuando el servidor ascendente está inactivo + * o devuelve un error inesperado. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +@SuppressWarnings("serial") +public class BadGatewayException extends RuntimeException { + + /** + * Crea una nueva instancia de {@code BadGatewayException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public BadGatewayException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code BadGatewayException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public BadGatewayException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code BadGatewayException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public BadGatewayException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/BadRequestException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/BadRequestException.java new file mode 100644 index 0000000..6ce30ef --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/BadRequestException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando se recibe una solicitud con datos inválidos o mal formados. + * Por ejemplo, cuando un campo requerido está vacío o un valor no cumple con las restricciones + * del sistema. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class BadRequestException extends RuntimeException { + + private static final long serialVersionUID = -6954469492272938899L; + + /** + * Crea una nueva instancia de {@code BadRequestException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public BadRequestException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code BadRequestException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public BadRequestException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code BadRequestException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public BadRequestException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/ConflictException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ConflictException.java new file mode 100644 index 0000000..449a791 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ConflictException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando se produce un conflicto en el estado actual del recurso. + * Por ejemplo, cuando se intenta actualizar un recurso que ha sido modificado por otro usuario + * o cuando se intenta realizar una operación que no es válida debido al estado actual del sistema. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class ConflictException extends RuntimeException { + + private static final long serialVersionUID = -2065645862249312298L; + + /** + * Crea una nueva instancia de {@code ConflictException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public ConflictException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code ConflictException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public ConflictException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code ConflictException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public ConflictException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/ForbiddenException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ForbiddenException.java new file mode 100644 index 0000000..263b752 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ForbiddenException.java @@ -0,0 +1,46 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando se intenta acceder a un recurso o realizar una operación + * que no está permitida para el usuario actual. Por ejemplo, cuando un usuario intenta + * acceder a un recurso que requiere permisos especiales o cuando intenta realizar una + * acción que no está autorizada. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class ForbiddenException extends RuntimeException { + + private static final long serialVersionUID = -1825202221085820141L; + + /** + * Crea una nueva instancia de {@code ForbiddenException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public ForbiddenException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code ForbiddenException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public ForbiddenException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code ForbiddenException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public ForbiddenException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/InternalServerErrorException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/InternalServerErrorException.java new file mode 100644 index 0000000..03e9fed --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/InternalServerErrorException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando ocurre un error interno en el servidor que impide + * completar la solicitud. Esto puede deberse a problemas de configuración, errores + * en el código del servidor o fallos inesperados. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class InternalServerErrorException extends RuntimeException { + + private static final long serialVersionUID = 1081785471638808116L; + + /** + * Crea una nueva instancia de {@code InternalServerErrorException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public InternalServerErrorException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code InternalServerErrorException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public InternalServerErrorException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code InternalServerErrorException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public InternalServerErrorException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/NotFoundException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/NotFoundException.java new file mode 100644 index 0000000..eda4ce1 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/NotFoundException.java @@ -0,0 +1,44 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando un recurso solicitado no se encuentra en el sistema. + * Esto puede ocurrir, por ejemplo, cuando se intenta acceder a un recurso + * que no existe o ha sido eliminado. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class NotFoundException extends RuntimeException { + + private static final long serialVersionUID = -8503378655195825178L; + + /** + * Crea una nueva instancia de {@code NotFoundException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public NotFoundException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code NotFoundException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public NotFoundException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code NotFoundException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public NotFoundException(Throwable cause) { + super(cause); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/ServiceUnavailableException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ServiceUnavailableException.java new file mode 100644 index 0000000..21b68e4 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ServiceUnavailableException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando un servicio no está disponible temporalmente. + * Esto puede ocurrir, por ejemplo, cuando el servidor está en mantenimiento + * o cuando hay problemas de conectividad con un servicio externo. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class ServiceUnavailableException extends RuntimeException { + + private static final long serialVersionUID = 2007517776804187799L; + + /** + * Crea una nueva instancia de {@code ServiceUnavailableException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public ServiceUnavailableException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code ServiceUnavailableException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public ServiceUnavailableException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code ServiceUnavailableException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public ServiceUnavailableException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/TeapotException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/TeapotException.java new file mode 100644 index 0000000..60a5b2a --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/TeapotException.java @@ -0,0 +1,46 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando se recibe un código de estado HTTP 418 (I'm a teapot). + * Esta excepción indica que el servidor se niega a preparar café porque es una tetera. + * Es una broma del protocolo HTTP y no debe ser utilizada en aplicaciones reales, sin embargo, + * la uso como excepción cuando alguien accede a un recurso que no debería. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class TeapotException extends RuntimeException { + + private static final long serialVersionUID = 6105284989060090791L; + + /** + * Crea una nueva instancia de {@code TeapotException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public TeapotException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code TeapotException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public TeapotException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code TeapotException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public TeapotException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnauthorizedException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnauthorizedException.java new file mode 100644 index 0000000..ece5564 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnauthorizedException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando un usuario no está autorizado para realizar una acción + * o acceder a un recurso específico. Esto puede ocurrir, por ejemplo, cuando + * se intenta acceder a un recurso sin las credenciales adecuadas o sin los permisos necesarios. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class UnauthorizedException extends RuntimeException { + + private static final long serialVersionUID = -3536275114764799718L; + + /** + * Crea una nueva instancia de {@code UnauthorizedException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public UnauthorizedException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code UnauthorizedException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public UnauthorizedException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code UnauthorizedException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public UnauthorizedException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnprocessableEntityException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnprocessableEntityException.java new file mode 100644 index 0000000..a9e73f5 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnprocessableEntityException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando la solicitud no puede ser procesada debido a + * errores de validación o problemas con los datos proporcionados. + * Esto puede incluir datos faltantes, formatos incorrectos o valores no válidos. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class UnprocessableEntityException extends RuntimeException { + + private static final long serialVersionUID = 5492048796111026459L; + + /** + * Crea una nueva instancia de {@code UnprocessableEntityException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public UnprocessableEntityException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code UnprocessableEntityException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public UnprocessableEntityException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code UnprocessableEntityException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public UnprocessableEntityException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnsupportedMediaTypeException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnsupportedMediaTypeException.java new file mode 100644 index 0000000..9135d87 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/UnsupportedMediaTypeException.java @@ -0,0 +1,45 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando el tipo de medio (MIME type) de una solicitud no es compatible + * con lo que el servidor puede procesar. Esto puede ocurrir, por ejemplo, cuando se envía + * un tipo de contenido no soportado en una solicitud HTTP. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class UnsupportedMediaTypeException extends RuntimeException { + + private static final long serialVersionUID = 1829890832415237556L; + + /** + * Crea una nueva instancia de {@code UnsupportedMediaTypeException} con un mensaje descriptivo. + * + * @param message El mensaje que describe el error. + */ + public UnsupportedMediaTypeException(String message) { + super(message); + } + + /** + * Crea una nueva instancia de {@code UnsupportedMediaTypeException} con un mensaje y una causa. + * + * @param message El mensaje que describe el error. + * @param cause La causa original de esta excepción. + */ + public UnsupportedMediaTypeException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Crea una nueva instancia de {@code UnsupportedMediaTypeException} con una causa. + * + * @param cause La causa original de esta excepción. + */ + public UnsupportedMediaTypeException(Throwable cause) { + super(cause); + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/exceptions/ValidationException.java b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ValidationException.java new file mode 100644 index 0000000..c5e289c --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/exceptions/ValidationException.java @@ -0,0 +1,28 @@ +package net.miarma.api.backlib.exceptions; + +/** + * Excepción lanzada cuando los datos proporcionados no cumplen con las reglas de validación + * establecidas en el sistema. Esto puede ocurrir, por ejemplo, cuando se envían datos + * incompletos o incorrectos en una solicitud. + * + *

Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario + * declararla explícitamente en los métodos que la lanzan.

+ * + * @author José Manuel Amador Gallardo + */ +public class ValidationException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 7857157229263093210L; + + /** + * Crea una nueva instancia de {@code ValidationException} con un mensaje descriptivo. + * + * @param json El JSON que describe el error de validación. + */ + public ValidationException(String json) { + super(json); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/gson/APIDontReturnExclusionStrategy.java b/backlib/src/main/java/net/miarma/api/backlib/gson/APIDontReturnExclusionStrategy.java new file mode 100644 index 0000000..5ae6b86 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/gson/APIDontReturnExclusionStrategy.java @@ -0,0 +1,25 @@ +package net.miarma.api.backlib.gson; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import net.miarma.api.backlib.annotations.APIDontReturn; + +/** + * Estrategia de exclusión para Gson que omite campos anotados con @APIDontReturn. + * Esta estrategia se utiliza para evitar que ciertos campos sean serializados + * y enviados en las respuestas de la API. + * + * @author José Manuel Amador Gallardo + */ +public class APIDontReturnExclusionStrategy implements ExclusionStrategy { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(APIDontReturn.class) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/gson/JsonObjectTypeAdapter.java b/backlib/src/main/java/net/miarma/api/backlib/gson/JsonObjectTypeAdapter.java new file mode 100644 index 0000000..792f101 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/gson/JsonObjectTypeAdapter.java @@ -0,0 +1,37 @@ +package net.miarma.api.backlib.gson; + +import com.google.gson.*; +import io.vertx.core.json.JsonObject; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * Adaptador de tipo para Gson que maneja la serialización y deserialización de objetos JsonObject. + * Este adaptador asegura que los objetos JsonObject se serialicen correctamente sin incluir el mapa interno. + * + * @author José Manuel Amador Gallardo + */ +public class JsonObjectTypeAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(JsonObject src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject safe = src == null ? new JsonObject() : src; + JsonObject wrapped = new JsonObject(safe.getMap()); // evita el map dentro + return context.serialize(wrapped.getMap()); + } + + @Override + public JsonObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (!json.isJsonObject()) { + throw new JsonParseException("Expected JsonObject"); + } + + JsonObject obj = new JsonObject(); + for (Map.Entry entry : json.getAsJsonObject().entrySet()) { + obj.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class)); + } + + return obj; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/gson/LocalDateTimeAdapter.java b/backlib/src/main/java/net/miarma/api/backlib/gson/LocalDateTimeAdapter.java new file mode 100644 index 0000000..bc17d8b --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/gson/LocalDateTimeAdapter.java @@ -0,0 +1,29 @@ +package net.miarma.api.backlib.gson; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Adaptador de tipo para Gson que maneja la serialización y deserialización de LocalDateTime. + * Este adaptador utiliza el formato ISO_LOCAL_DATE_TIME para convertir LocalDateTime a String + * y viceversa. + * + * @author José Manuel Amador Gallardo + */ +public class LocalDateTimeAdapter implements JsonSerializer, JsonDeserializer { + private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + + @Override + public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.format(formatter)); + } + + @Override + public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return LocalDateTime.parse(json.getAsString(), formatter); + } +} + diff --git a/backlib/src/main/java/net/miarma/api/backlib/gson/ValuableEnumDeserializer.java b/backlib/src/main/java/net/miarma/api/backlib/gson/ValuableEnumDeserializer.java new file mode 100644 index 0000000..fde87b7 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/gson/ValuableEnumDeserializer.java @@ -0,0 +1,30 @@ +package net.miarma.api.backlib.gson; + +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 java.lang.reflect.Type; +import java.util.Arrays; + +/** + * Deserializador de Gson para enumeraciones que implementan ValuableEnum. + * Este deserializador convierte un valor entero en una instancia de la enumeración correspondiente. + * + * @author José Manuel Amador Gallardo + */ +public class ValuableEnumDeserializer implements JsonDeserializer { + + @Override + public ValuableEnum 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) + .findFirst() + .orElseThrow(() -> new JsonParseException("Invalid enum value: " + value)); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/gson/ValuableEnumTypeAdapter.java b/backlib/src/main/java/net/miarma/api/backlib/gson/ValuableEnumTypeAdapter.java new file mode 100644 index 0000000..89e92b0 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/gson/ValuableEnumTypeAdapter.java @@ -0,0 +1,23 @@ +package net.miarma.api.backlib.gson; + +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 java.lang.reflect.Type; + +/** + * Adaptador de tipo para Gson que maneja la serialización de enumeraciones que implementan ValuableEnum. + * Este adaptador convierte el valor de la enumeración en un elemento JSON primitivo. + * + * @author José Manuel Amador Gallardo + */ +public class ValuableEnumTypeAdapter implements JsonSerializer { + + @Override + public JsonElement serialize(ValuableEnum src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getValue()); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/http/ApiResponse.java b/backlib/src/main/java/net/miarma/api/backlib/http/ApiResponse.java new file mode 100644 index 0000000..d5d9150 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/http/ApiResponse.java @@ -0,0 +1,60 @@ +package net.miarma.api.backlib.http; + +/** + * Clase genérica para representar una respuesta de la API. + *

+ * Esta clase encapsula el estado de la respuesta, un mensaje descriptivo y los datos devueltos. + * Se utiliza para estandarizar las respuestas de la API y facilitar el manejo de errores y datos. + *

+ * Ejemplo de uso: + *

+ *     ApiResponse response = new ApiResponse<>(ApiStatus.SUCCESS, "Data retrieved successfully", myData);
+ * 
+ * @see ApiStatus + * + * @param Tipo de dato que contendrá la respuesta. + * + * @author José Manuel Amador Gallardo + */ +public class ApiResponse { + private final int status; + private final String message; + private final T data; + + /** + * Constructor para crear una respuesta de la API con un estado, mensaje y datos. + * + * @param status El estado de la respuesta, representado por un código. + * @param message Un mensaje descriptivo de la respuesta. + * @param data Los datos devueltos en la respuesta, puede ser null si no hay datos. + */ + public ApiResponse(ApiStatus status, String message, T data) { + this.status = status.getCode(); + this.message = message; + this.data = data; + } + + /** + * Constructor para crear una respuesta de la API con un estado y mensaje, sin datos. + * + * @param status El estado de la respuesta, representado por un código. + * @param message Un mensaje descriptivo de la respuesta. + */ + public ApiResponse(ApiStatus status, String message) { + this(status, message, null); + } + + public int getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public T getData() { + return data; + } + +} + diff --git a/backlib/src/main/java/net/miarma/api/backlib/http/ApiStatus.java b/backlib/src/main/java/net/miarma/api/backlib/http/ApiStatus.java new file mode 100644 index 0000000..e6aea04 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/http/ApiStatus.java @@ -0,0 +1,108 @@ +package net.miarma.api.backlib.http; + +import net.miarma.api.backlib.exceptions.*; + +/** + * Enum que representa los códigos de estado HTTP utilizados en la API. + * + * @author José Manuel Amador Gallardo + */ +public enum ApiStatus { + OK(200), + CREATED(201), + ACCEPTED(202), + NO_CONTENT(204), + BAD_REQUEST(400), + UNAUTHORIZED(401), + FORBIDDEN(403), + NOT_FOUND(404), + CONFLICT(409), + IM_A_TEAPOT(418), + UNPROCESSABLE_ENTITY(422), + UNSUPPORTED_MEDIA_TYPE(415), + TOO_MANY_REQUESTS(429), + INTERNAL_SERVER_ERROR(500), + SERVICE_UNAVAILABLE(503); + + private final int code; + + ApiStatus(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + /** + * Obtiene el mensaje por defecto asociado al código de estado. + * + * @return El mensaje por defecto. + */ + public String getDefaultMessage() { + return switch (this) { + case OK -> "OK"; + case CREATED -> "Created"; + case ACCEPTED -> "Accepted"; + case NO_CONTENT -> "No Content"; + case BAD_REQUEST -> "Bad Request"; + case UNAUTHORIZED -> "Unauthorized"; + case FORBIDDEN -> "Forbidden"; + case NOT_FOUND -> "Not Found"; + case CONFLICT -> "Conflict"; + case IM_A_TEAPOT -> "The server refuses the attempt to brew coffee with a teapot"; + case UNPROCESSABLE_ENTITY -> "Unprocessable Entity"; + case UNSUPPORTED_MEDIA_TYPE -> "Unsupported Media Type"; + case TOO_MANY_REQUESTS -> "Too many requests"; + case INTERNAL_SERVER_ERROR -> "Internal Server Error"; + case SERVICE_UNAVAILABLE -> "Service Unavailable"; + }; + } + + /** + * Crea un ApiStatus a partir de una excepción. + * @param t la excepción que se desea convertir a ApiStatus + * @return ApiStatus correspondiente a la excepción, o INTERNAL_SERVER_ERROR si no se reconoce la excepción + */ + public static ApiStatus fromException(Throwable t) { + if (t instanceof NotFoundException) return ApiStatus.NOT_FOUND; + if (t instanceof BadRequestException) return ApiStatus.BAD_REQUEST; + if (t instanceof UnauthorizedException) return ApiStatus.UNAUTHORIZED; + if (t instanceof ForbiddenException) return ApiStatus.FORBIDDEN; + if (t instanceof ConflictException) return ApiStatus.CONFLICT; + if (t instanceof TeapotException) return ApiStatus.IM_A_TEAPOT; + if (t instanceof ServiceUnavailableException) return ApiStatus.SERVICE_UNAVAILABLE; + if (t instanceof UnprocessableEntityException) return ApiStatus.UNPROCESSABLE_ENTITY; + if (t instanceof UnsupportedMediaTypeException) return ApiStatus.UNSUPPORTED_MEDIA_TYPE; + if (t instanceof ValidationException) return ApiStatus.BAD_REQUEST; + return ApiStatus.INTERNAL_SERVER_ERROR; + } + + /** + * Obtiene el ApiStatus correspondiente al código de estado HTTP. + * + * @param code El código de estado HTTP. + * @return El ApiStatus correspondiente, o null si no se encuentra. + */ + public static ApiStatus fromCode(int code) { + return switch (code) { + case 200 -> OK; + case 201 -> CREATED; + case 202 -> ACCEPTED; + case 204 -> NO_CONTENT; + case 400 -> BAD_REQUEST; + case 401 -> UNAUTHORIZED; + case 403 -> FORBIDDEN; + case 404 -> NOT_FOUND; + case 409 -> CONFLICT; + case 418 -> IM_A_TEAPOT; + case 422 -> UNPROCESSABLE_ENTITY; + case 415 -> UNSUPPORTED_MEDIA_TYPE; + case 429 -> TOO_MANY_REQUESTS; + case 500 -> INTERNAL_SERVER_ERROR; + case 503 -> SERVICE_UNAVAILABLE; + default -> null; + }; + } +} + diff --git a/backlib/src/main/java/net/miarma/api/backlib/http/QueryFilters.java b/backlib/src/main/java/net/miarma/api/backlib/http/QueryFilters.java new file mode 100644 index 0000000..b2bb013 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/http/QueryFilters.java @@ -0,0 +1,80 @@ +package net.miarma.api.backlib.http; + +import io.vertx.ext.web.RoutingContext; + +import java.util.Optional; + +/** + * Representa los filtros de consulta para una solicitud HTTP. + * Esta clase encapsula los parámetros de ordenamiento, límite y desplazamiento + * que se pueden aplicar a una consulta. + * + * @author José Manuel Amador Gallardo + */ +public class QueryFilters { + + private Optional sort = Optional.empty(); + private Optional order = Optional.of("ASC"); + private Optional limit = Optional.empty(); + private Optional offset = Optional.empty(); + + public QueryFilters() {} + + public QueryFilters(Optional sort, Optional order, Optional limit, Optional offset) { + this.sort = sort; + this.order = order; + this.limit = limit; + this.offset = offset; + } + + public Optional getSort() { + return sort; + } + + public void setSort(String sort) { + this.sort = Optional.ofNullable(sort); + } + + public Optional getOrder() { + return order; + } + + public void setOrder(String order) { + this.order = Optional.ofNullable(order); + } + + public Optional getLimit() { + return limit; + } + + public void setLimit(Integer limit) { + this.limit = Optional.ofNullable(limit); + } + + public Optional getOffset() { + return offset; + } + + public void setOffset(Integer offset) { + this.offset = Optional.ofNullable(offset); + } + + @Override + public String toString() { + return "QueryFilters{" + + "sort=" + sort + + ", order=" + order + + ", limit=" + limit + + ", offset=" + offset + + '}'; + } + + public static QueryFilters from(RoutingContext ctx) { + QueryFilters filters = new QueryFilters(); + filters.setSort(ctx.request().getParam("_sort")); + filters.setOrder(ctx.request().getParam("_order")); + filters.setLimit(ctx.request().getParam("_limit") != null ? Integer.parseInt(ctx.request().getParam("_limit")) : null); + filters.setOffset(ctx.request().getParam("_offset") != null ? Integer.parseInt(ctx.request().getParam("_offset")) : null); + return filters; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/http/QueryParams.java b/backlib/src/main/java/net/miarma/api/backlib/http/QueryParams.java new file mode 100644 index 0000000..ee7ea58 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/http/QueryParams.java @@ -0,0 +1,85 @@ +package net.miarma.api.backlib.http; + +import io.vertx.ext.web.RoutingContext; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Representa los parámetros de consulta para una solicitud HTTP. + * Esta clase encapsula los filtros de consulta y los filtros adicionales + * que se pueden aplicar a una consulta. + * + * @author José Manuel Amador Gallardo + */ +public class QueryParams { + + private final Map filters; + private final QueryFilters queryFilters; + + public QueryParams(Map filters, QueryFilters queryFilters) { + this.filters = filters; + this.queryFilters = queryFilters; + } + + public Map getFilters() { + return filters; + } + + public QueryFilters getQueryFilters() { + return queryFilters; + } + + public static QueryParams from(RoutingContext ctx) { + Map filters = new HashMap<>(); + + QueryFilters queryFilters = QueryFilters.from(ctx); + + ctx.queryParams().forEach(entry -> { + String key = entry.getKey(); + String value = entry.getValue(); + + if (!key.startsWith("_")) { // esto es un filtro válido + filters.put(key, value); + } + }); + + return new QueryParams(filters, queryFilters); + } + + public static QueryParams filterForEntity(QueryParams original, Class entityClass, String prefix) { + Set validKeys = getFieldNames(entityClass); + + Map filtered = original.getFilters().entrySet().stream() + .filter(e -> { + String key = e.getKey(); + return key.startsWith(prefix + ".") && validKeys.contains(key.substring(prefix.length() + 1)); + }) + .collect(Collectors.toMap( + e -> e.getKey().substring(prefix.length() + 1), // quitar el prefijo + Map.Entry::getValue + )); + + return new QueryParams(filtered, original.getQueryFilters()); + } + + + + private static Set getFieldNames(Class clazz) { + return Arrays.stream(clazz.getDeclaredFields()) + .map(Field::getName) + .collect(Collectors.toSet()); + } + + @Override + public String toString() { + return "QueryParams{" + + "filters=" + filters + + ", queryFilters=" + queryFilters + + '}'; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/http/SingleJsonResponse.java b/backlib/src/main/java/net/miarma/api/backlib/http/SingleJsonResponse.java new file mode 100644 index 0000000..6d3107b --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/http/SingleJsonResponse.java @@ -0,0 +1,14 @@ +package net.miarma.api.backlib.http; + +/** + * Representa una respuesta JSON que contiene un único mensaje. + * Esta clase se utiliza para encapsular una respuesta simple en formato JSON. + * + * @param el tipo del mensaje que se envía en la respuesta + * @author José Manuel Amador Gallardo + */ +public record SingleJsonResponse(T message) { + public static SingleJsonResponse of(T message) { + return new SingleJsonResponse<>(message); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/interfaces/IUser.java b/backlib/src/main/java/net/miarma/api/backlib/interfaces/IUser.java new file mode 100644 index 0000000..d21fcfd --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/interfaces/IUser.java @@ -0,0 +1,21 @@ +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; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/interfaces/IUserRole.java b/backlib/src/main/java/net/miarma/api/backlib/interfaces/IUserRole.java new file mode 100644 index 0000000..d46ad4f --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/interfaces/IUserRole.java @@ -0,0 +1,7 @@ +package net.miarma.api.backlib.interfaces; + +import net.miarma.api.backlib.ValuableEnum; + +public interface IUserRole extends ValuableEnum { + String name(); +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/middlewares/AbstractAuthGuard.java b/backlib/src/main/java/net/miarma/api/backlib/middlewares/AbstractAuthGuard.java new file mode 100644 index 0000000..3caa063 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/middlewares/AbstractAuthGuard.java @@ -0,0 +1,76 @@ +package net.miarma.api.backlib.middlewares; + +import java.util.function.Consumer; + +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.interfaces.IUserRole; +import net.miarma.api.backlib.security.JWTManager; +import net.miarma.api.backlib.util.JsonUtil; + +/** + * Base para AuthGuards de microservicios. + * Maneja extracción de JWT y verificación básica. + * Los microservicios solo implementan getUserEntity y hasPermission. + */ +@SuppressWarnings("unchecked") // arreglar el warning de heap pollution de los arrays de genéricos +public abstract class AbstractAuthGuard & IUserRole> { + + protected abstract R parseRole(String roleStr); + protected abstract void getUserEntity(int userId, RoutingContext ctx, Consumer callback); + protected abstract boolean hasPermission(U user, R role); + + public Handler check(R... allowedRoles) { + return ctx -> { + String token = extractToken(ctx); + if (token == null || !JWTManager.getInstance().isValid(token)) { + JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, "Invalid or missing token"); + return; + } + + int userId = JWTManager.getInstance().extractUserId(token); + String roleStr = JWTManager.getInstance().extractRole(token); + + R role; + try { + role = parseRole(roleStr); + } catch (Exception e) { + JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, "Invalid role"); + return; + } + + ctx.put("userId", userId); + ctx.put("role", role); + + getUserEntity(userId, ctx, entity -> { + if (entity == null) { + JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, "User not found"); + return; + } + + if (allowedRoles.length == 0 || isRoleAllowed(role, allowedRoles)) { + ctx.put("userEntity", entity); + ctx.next(); + } else { + JsonUtil.sendJson(ctx, ApiStatus.FORBIDDEN, "Forbidden"); + } + }); + }; + } + + private boolean isRoleAllowed(R role, R... allowedRoles) { + for (R allowed : allowedRoles) { + if (role == allowed) return true; + } + return false; + } + + private String extractToken(RoutingContext ctx) { + String authHeader = ctx.request().getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/security/DNIValidator.java b/backlib/src/main/java/net/miarma/api/backlib/security/DNIValidator.java new file mode 100644 index 0000000..337ce8b --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/security/DNIValidator.java @@ -0,0 +1,66 @@ +package net.miarma.api.backlib.security; + +/** + * Validador de DNI/NIE español. + *

+ * Este validador comprueba si un DNI o NIE es válido según las reglas establecidas por la legislación española. + * Un DNI debe tener 8 dígitos seguidos de una letra, y un NIE debe comenzar con X, Y o Z seguido de 7 dígitos y una letra. + * + * @author José Manuel Amador Gallardo + */ +public class DNIValidator { + + /** + * Valida un DNI o NIE español. + * + * @param id El DNI o NIE a validar. + * @return true si el DNI/NIE es válido, false en caso contrario. + */ + public static boolean isValid(String id) { + if (id == null || id.length() != 9) { + return false; + } + + id = id.toUpperCase(); // Pa evitar problemas con minúsculas + String numberPart; + char letterPart = id.charAt(8); + + if (id.startsWith("X") || id.startsWith("Y") || id.startsWith("Z")) { + // NIE + char prefix = id.charAt(0); + String numericPrefix = switch (prefix) { + case 'X' -> "0"; + case 'Y' -> "1"; + case 'Z' -> "2"; + default -> null; + }; + + if (numericPrefix == null) return false; + + numberPart = numericPrefix + id.substring(1, 8); + } else { + // DNI + numberPart = id.substring(0, 8); + } + + if (!numberPart.matches("\\d{8}")) { + return false; + } + + int number = Integer.parseInt(numberPart); + char expectedLetter = calculateLetter(number); + + return letterPart == expectedLetter; + } + + /** + * Calcula la letra correspondiente a un número de DNI. + * + * @param number El número del DNI (8 dígitos). + * @return La letra correspondiente. + */ + private static char calculateLetter(int number) { + String letters = "TRWAGMYFPDXBNJZSQVHLCKE"; + return letters.charAt(number % 23); + } +} 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 new file mode 100644 index 0000000..bc2b5a5 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/security/JWTManager.java @@ -0,0 +1,161 @@ +package net.miarma.api.backlib.security; + +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.Constants.CoreUserRole; + +import java.util.Date; + +/** + * Clase de gestión de JSON Web Tokens (JWT). + * Proporciona métodos para generar, verificar y decodificar tokens JWT. + *

+ * 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 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(); + } + + /** + * 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, CoreUserRole 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); + } + + /** + * Decodifica un token JWT sin verificar su firma. + * + * @param token El token JWT a decodificar. + * @return Un objeto DecodedJWT que contiene la información del token. + */ + public DecodedJWT decodeWithoutVerification(String token) { + return JWT.decode(token); + } + + /** + * Verifica la validez de un token JWT. + * + * @param token El token JWT a verificar. + * @return true si el token es válido, false en caso contrario. + */ + public boolean isValid(String token) { + try { + verifier.verify(token); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Verifica si un token JWT pertenece a un usuario administrador. + * + * @param token El token JWT a verificar. + * @return true si el token pertenece a un administrador, false en caso contrario. + */ + public boolean isAdmin(String token) { + 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; + } + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/security/PasswordHasher.java b/backlib/src/main/java/net/miarma/api/backlib/security/PasswordHasher.java new file mode 100644 index 0000000..15a099f --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/security/PasswordHasher.java @@ -0,0 +1,21 @@ +package net.miarma.api.backlib.security; +import org.mindrot.jbcrypt.BCrypt; + +/** + * Clase de utilidad para el hash de contraseñas. + * Utiliza BCrypt para generar y verificar hashes de contraseñas. + * + * @author José Manuel Amador Gallardo + */ +public class PasswordHasher { + + private static final int SALT_ROUNDS = 12; + + public static String hash(String plainPassword) { + return BCrypt.hashpw(plainPassword, BCrypt.gensalt(SALT_ROUNDS)); + } + + public static boolean verify(String plainPassword, String hashedPassword) { + return BCrypt.checkpw(plainPassword, hashedPassword); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/security/SecretManager.java b/backlib/src/main/java/net/miarma/api/backlib/security/SecretManager.java new file mode 100644 index 0000000..9cd38af --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/security/SecretManager.java @@ -0,0 +1,81 @@ +package net.miarma.api.backlib.security; + +import net.miarma.api.backlib.ConfigManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.security.SecureRandom; +import java.util.List; +import java.util.Properties; + +/** + * 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. + * Si no, genera un nuevo secret de 64 bytes, lo guarda en el archivo de configuración + * y lo devuelve. + *

+ * Esta clase sigue el patron Singleton para asegurar una sola instancia. + * @author José Manuel Amador Gallardo + */ +public class SecretManager { + + private static String cachedSecret = null; + + public static String getOrCreateSecret() { + if (cachedSecret != null) return cachedSecret; + + try { + File configFile = ConfigManager.getInstance().getConfigFile(); + Properties config = new Properties(); + + if (configFile.exists()) { + try (FileInputStream fis = new FileInputStream(configFile)) { + config.load(fis); + } + } + + String secret = config.getProperty("jwt.secret"); + if (secret != null && !secret.trim().isEmpty()) { + cachedSecret = secret.trim(); + } else { + cachedSecret = generateSecret(64); + + List lines = Files.readAllLines(configFile.toPath()); + + boolean replaced = false; + for (int i = 0; i < lines.size(); i++) { + if (lines.get(i).trim().startsWith("jwt.secret=")) { + lines.set(i, "jwt.secret=" + cachedSecret); + replaced = true; + break; + } + } + + if (!replaced) { + lines.add("# Security Configuration"); + lines.add("jwt.secret=" + cachedSecret); + } + + Files.write(configFile.toPath(), lines); + } + + return cachedSecret; + + } catch (IOException e) { + throw new RuntimeException("Could not create or get the secret", e); + } + } + + private static String generateSecret(int byteLength) { + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[byteLength]; + random.nextBytes(bytes); + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/security/SusPather.java b/backlib/src/main/java/net/miarma/api/backlib/security/SusPather.java new file mode 100644 index 0000000..e267eee --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/security/SusPather.java @@ -0,0 +1,24 @@ +package net.miarma.api.backlib.security; + +/** + * Clase que verifica si una ruta es sospechosa o no. + * Utilizada para evitar el acceso a rutas potencialmente peligrosas. + * + * @author José Manuel Amador Gallardo + */ +public class SusPather { + public static boolean isSusPath(String path) { + return path.endsWith(".env") || + path.endsWith(".git") || + path.endsWith(".DS_Store") || + path.endsWith("wp-login.php") || + path.endsWith("admin.php") || + path.contains(".git/") || + path.contains(".svn/") || + path.contains(".idea/") || + path.contains(".vscode/") || + path.contains(".settings/") || + path.contains(".classpath") || + path.contains(".project"); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/DateParser.java b/backlib/src/main/java/net/miarma/api/backlib/util/DateParser.java new file mode 100644 index 0000000..40fc95e --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/DateParser.java @@ -0,0 +1,14 @@ +package net.miarma.api.backlib.util; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +/** + * Clase de utilidad para convertir fechas a segundos desde la época Unix. + * @author José Manuel Amador Gallardo + */ +public class DateParser { + public static long parseDate(LocalDateTime date) { + return date.toEpochSecond(ZoneOffset.UTC); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/DeploymentUtil.java b/backlib/src/main/java/net/miarma/api/backlib/util/DeploymentUtil.java new file mode 100644 index 0000000..f0b9946 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/DeploymentUtil.java @@ -0,0 +1,20 @@ +package net.miarma.api.backlib.util; + +/** + * Clase de utilidad para mensajes de despliegue. + * @author José Manuel Amador Gallardo + */ +public class DeploymentUtil { + + public static String successMessage(Class clazz) { + return String.join(" ", "🟢", clazz.getSimpleName(), "deployed successfully"); + } + + public static String failMessage(Class clazz, Throwable e) { + return String.join(" ", "🔴 Error deploying", clazz.getSimpleName()+":", e.getMessage()); + } + + public static String apiUrlMessage(String host, Integer port) { + return String.join(" ", "\t🔗 API URL:", host+":"+port); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/EventBusUtil.java b/backlib/src/main/java/net/miarma/api/backlib/util/EventBusUtil.java new file mode 100644 index 0000000..ed4af2b --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/EventBusUtil.java @@ -0,0 +1,60 @@ +package net.miarma.api.backlib.util; + +import io.vertx.core.Handler; +import io.vertx.core.eventbus.Message; +import io.vertx.core.eventbus.ReplyException; +import io.vertx.ext.web.RoutingContext; +import net.miarma.api.backlib.http.ApiStatus; + +/** + * Clase de utilidad para manejar errores en el EventBus. + * @author José Manuel Amador Gallardo + */ +public class EventBusUtil { + public static Handler fail(Message msg) { + return err -> { + if(err instanceof ReplyException re) { + msg.fail(re.failureCode(), re.getMessage()); + } else { + ApiStatus status = ApiStatus.fromException(err); + msg.fail(status.getCode(), err.getMessage()); + } + }; + } + + public static Handler fail(Throwable err) { + return _ -> { + ApiStatus status = ApiStatus.fromException(err); + throw new RuntimeException(status.getDefaultMessage(), err); + }; + } + + public static void handleReplyError(RoutingContext ctx, Throwable err) { + if (err instanceof ReplyException replyEx) { + int code = replyEx.failureCode(); + String message = replyEx.getMessage(); + + ApiStatus status = ApiStatus.fromCode(code); + if (status == null) status = ApiStatus.INTERNAL_SERVER_ERROR; + + JsonUtil.sendJson(ctx, status, null, message); + } else { + JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, "Internal server error"); + } + } + + public static void handleReplyError(RoutingContext ctx, Throwable err, String fallbackMsg) { + if (err instanceof ReplyException replyEx) { + int code = replyEx.failureCode(); + String message = replyEx.getMessage(); + + ApiStatus status = ApiStatus.fromCode(code); + if (status == null) status = ApiStatus.INTERNAL_SERVER_ERROR; + + JsonUtil.sendJson(ctx, status, null, message != null ? message : fallbackMsg); + } else { + JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, fallbackMsg); + } + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/JsonUtil.java b/backlib/src/main/java/net/miarma/api/backlib/util/JsonUtil.java new file mode 100644 index 0000000..985259f --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/JsonUtil.java @@ -0,0 +1,33 @@ +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.http.ApiResponse; +import net.miarma.api.backlib.http.ApiStatus; + +/** + * Clase de utilidad para enviar respuestas JSON. + * @author José Manuel Amador Gallardo + */ +public class JsonUtil { + public static void sendJson(RoutingContext ctx, ApiStatus status, T data) { + sendJson(ctx, status, data, status.getDefaultMessage()); + } + + public static void sendJson(RoutingContext ctx, ApiStatus status, T data, String message) { + ctx.response().putHeader("Content-Type", "application/json").setStatusCode(status.getCode()); + + if (data instanceof JsonObject || data instanceof JsonArray) { + JsonObject response = new JsonObject() + .put("status", status.getCode()) + .put("message", message) + .put("data", data); + ctx.response().end(response.encode()); + } else { + ctx.response().end(Constants.GSON.toJson(new ApiResponse<>(status, message, data))); + } + } + +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/MessageUtil.java b/backlib/src/main/java/net/miarma/api/backlib/util/MessageUtil.java new file mode 100644 index 0000000..c6a22d7 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/MessageUtil.java @@ -0,0 +1,15 @@ +package net.miarma.api.backlib.util; + +/** + * Clase de utilidad para mensajes comunes en la API. + * @author José Manuel Amador Gallardo + */ +public class MessageUtil { + public static String notFound(String what, String where) { + return String.join(" ", "❌", what, "not found in", where); + } + + public static String failedTo(String action, String on, Throwable e) { + return String.join(" ", "❌ Failed to", action, on+":", e.getMessage()); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/NameCensorer.java b/backlib/src/main/java/net/miarma/api/backlib/util/NameCensorer.java new file mode 100644 index 0000000..ab16f39 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/NameCensorer.java @@ -0,0 +1,35 @@ +package net.miarma.api.backlib.util; + +/** + * Clase de utilidad para censurar nombres. + * Censura los nombres dejando las primeras 3 letras visibles y el resto con asteriscos. + * Si el nombre es muy largo, lo acorta a 16 caracteres y añade "..." al final. + * @author José Manuel Amador Gallardo + */ +public class NameCensorer { + + public static String censor(String nombre) { + if (nombre == null || nombre.isBlank()) return ""; + + String[] palabras = nombre.trim().split("\\s+"); + + for (int i = 0; i < palabras.length; i++) { + String palabra = palabras[i]; + int len = palabra.length(); + + if (len > 3) { + palabras[i] = palabra.substring(0, 3) + "*".repeat(len - 3); + } else if (len > 0) { + palabras[i] = palabra.charAt(0) + "*".repeat(len - 1); + } + } + + String censurado = String.join(" ", palabras); + + if (censurado.length() > 16) { + censurado = censurado.substring(0, 16) + "..."; + } + + return censurado; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/PasteKeyGenerator.java b/backlib/src/main/java/net/miarma/api/backlib/util/PasteKeyGenerator.java new file mode 100644 index 0000000..23d4fec --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/PasteKeyGenerator.java @@ -0,0 +1,17 @@ +package net.miarma.api.backlib.util; + +import java.security.SecureRandom; + +public class PasteKeyGenerator { + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static final SecureRandom RANDOM = new SecureRandom(); + + public static String generate(int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + int index = RANDOM.nextInt(ALPHABET.length()); + sb.append(ALPHABET.charAt(index)); + } + return sb.toString(); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/RateLimiter.java b/backlib/src/main/java/net/miarma/api/backlib/util/RateLimiter.java new file mode 100644 index 0000000..682c826 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/RateLimiter.java @@ -0,0 +1,29 @@ +package net.miarma.api.backlib.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.time.Instant; + +public class RateLimiter { + private static final int MAX_REQUESTS = 5; // max 5 requests + private static final long WINDOW_MS = 60_000; // 1 minuto + private Map requests = new ConcurrentHashMap<>(); + + static class UserRequests { + int count; + long windowStart; + } + + public boolean allow(String ip) { + long now = Instant.now().toEpochMilli(); + UserRequests ur = requests.getOrDefault(ip, new UserRequests()); + if (now - ur.windowStart > WINDOW_MS) { + ur.count = 1; + ur.windowStart = now; + } else { + ur.count++; + } + requests.put(ip, ur); + return ur.count <= MAX_REQUESTS; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/RouterUtil.java b/backlib/src/main/java/net/miarma/api/backlib/util/RouterUtil.java new file mode 100644 index 0000000..3867e4e --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/RouterUtil.java @@ -0,0 +1,88 @@ +package net.miarma.api.backlib.util; + +import io.vertx.ext.web.Router; +import net.miarma.api.backlib.Constants; + +/** + * Clase de utilidad para adjuntar un logger a un router de Vert.x. + * @author José Manuel Amador Gallardo + */ +public class RouterUtil { + + public static void attachLogger(Router router) { + router.route().handler(ctx -> { + long startTime = System.currentTimeMillis(); + + ctx.addBodyEndHandler(_ -> { + long duration = System.currentTimeMillis() - startTime; + + String method = ctx.request().method().name(); + String path = ctx.normalizedPath(); + String query = ctx.request().query(); + int status = ctx.response().getStatusCode(); + + String statusMessage = getStatusMessage(status); + String emoji = getEmoji(status); + + String formattedQuery = (query != null && !query.isEmpty()) ? "?" + query : ""; + + String clientIP = ctx.request().getHeader("X-Forwarded-For"); + if (clientIP != null && !clientIP.isBlank()) { + clientIP = clientIP.split(",")[0].trim(); // IP real del cliente + } else { + clientIP = ctx.request().remoteAddress().host(); // fallback + } + + + String log = String.format( + "%s [%d %s] %s %s%s (IP: %s) (⏱ %dms)", + emoji, + status, + statusMessage, + method, + path, + formattedQuery, + clientIP, + duration + ); + + Constants.LOGGER.info(log); + }); + + ctx.next(); + }); + } + + private static String getStatusMessage(int code) { + return switch (code) { + case 100 -> "Continue"; + case 101 -> "Switching Protocols"; + case 200 -> "OK"; + case 201 -> "Created"; + case 202 -> "Accepted"; + case 204 -> "No Content"; + case 301 -> "Moved Permanently"; + case 302 -> "Found"; + case 304 -> "Not Modified"; + case 400 -> "Bad Request"; + case 401 -> "Unauthorized"; + case 403 -> "Forbidden"; + case 404 -> "Not Found"; + case 409 -> "Conflict"; + case 415 -> "Unsupported Media Type"; + case 422 -> "Unprocessable Entity"; + case 500 -> "Internal Server Error"; + case 502 -> "Bad Gateway"; + case 503 -> "Service Unavailable"; + default -> "Unknown"; + }; + } + + private static String getEmoji(int statusCode) { + if (statusCode >= 200 && statusCode < 300) return "✅"; + if (statusCode >= 300 && statusCode < 400) return "🔁"; + if (statusCode >= 400 && statusCode < 500) return "❌"; + if (statusCode >= 500) return "💥"; + return "📥"; + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/util/UserNameGenerator.java b/backlib/src/main/java/net/miarma/api/backlib/util/UserNameGenerator.java new file mode 100644 index 0000000..77ed5b6 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/util/UserNameGenerator.java @@ -0,0 +1,26 @@ +package net.miarma.api.backlib.util; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Clase de utilidad para generar nombres de usuario únicos basados en un hash. + * Utiliza SHA-256 para crear un hash del nombre de usuario y lo convierte a hexadecimal. + * + * @author José Manuel Amador Gallardo + */ +public class UserNameGenerator { + public static String generateUserName(String baseName, String input, int hashBytesCount) throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + + StringBuilder hexHash = new StringBuilder(); + for (int i = 0; i < hashBytesCount && i < hash.length; i++) { + hexHash.append(String.format("%02x", hash[i])); + } + + return baseName + "-" + hexHash; + } +} + diff --git a/backlib/src/main/java/net/miarma/api/backlib/validation/ValidationResult.java b/backlib/src/main/java/net/miarma/api/backlib/validation/ValidationResult.java new file mode 100644 index 0000000..b1b3564 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/validation/ValidationResult.java @@ -0,0 +1,31 @@ +package net.miarma.api.backlib.validation; + +import java.util.HashMap; +import java.util.Map; + +/** + * Representa el resultado de una validación, conteniendo errores asociados a campos específicos. + * + * @author José Manuel Amador Gallardo + */ +public class ValidationResult { + + private final Map errors = new HashMap<>(); + + public ValidationResult addError(String field, String message) { + errors.put(field, message); + return this; + } + + public boolean isValid() { + return errors.isEmpty(); + } + + public Map getErrors() { + return errors; + } + + public String getFirstError() { + return errors.values().stream().findFirst().orElse(null); + } +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/validation/Validator.java b/backlib/src/main/java/net/miarma/api/backlib/validation/Validator.java new file mode 100644 index 0000000..dbb2782 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/validation/Validator.java @@ -0,0 +1,12 @@ +package net.miarma.api.backlib.validation; + +import io.vertx.core.Future; + +/** + * Interfaz para la validación de entidades. + * @param Tipo de entidad a validar. + * @author José Manuel Amador Gallardo + */ +public interface Validator { + Future validate(T entity); +} diff --git a/backlib/src/main/java/net/miarma/api/backlib/vertx/VertxJacksonConfig.java b/backlib/src/main/java/net/miarma/api/backlib/vertx/VertxJacksonConfig.java new file mode 100644 index 0000000..4479423 --- /dev/null +++ b/backlib/src/main/java/net/miarma/api/backlib/vertx/VertxJacksonConfig.java @@ -0,0 +1,26 @@ +package net.miarma.api.backlib.vertx; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.vertx.core.json.jackson.DatabindCodec; + +/** + * Configura el ObjectMapper de Vert.x para manejar correctamente + * fechas y tiempos. Esto es necesario para que las fechas se serialicen + * y deserialicen correctamente. + * + * @author José Manuel Amador Gallardo + */ +public class VertxJacksonConfig { + @SuppressWarnings("deprecation") + public static void configure() { + ObjectMapper mapper = DatabindCodec.mapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + ObjectMapper prettyBase = DatabindCodec.prettyMapper(); + prettyBase.registerModule(new JavaTimeModule()); + prettyBase.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + } +} diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/bootstrap/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml new file mode 100644 index 0000000..db880ce --- /dev/null +++ b/bootstrap/pom.xml @@ -0,0 +1,90 @@ + + 4.0.0 + + + net.miarma.api + miarma-ecosystem + 1.2.0 + + + bootstrap + jar + + + 23 + 23 + + + + + jitpack.io + https://jitpack.io + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + net.miarma.api + backlib + ${project.version} + + + net.miarma.api + core + ${project.version} + + + net.miarma.api + huertos + ${project.version} + + + net.miarma.api + huertosdecine + ${project.version} + + + net.miarma.api + miarmacraft + ${project.version} + + + net.miarma.api + mpaste + ${project.version} + + + + + MiarmaEcosystem + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.api.AppInitializer + + + + + + + + + diff --git a/bootstrap/src/main/java/net/miarma/api/AppInitializer.java b/bootstrap/src/main/java/net/miarma/api/AppInitializer.java new file mode 100644 index 0000000..befd14e --- /dev/null +++ b/bootstrap/src/main/java/net/miarma/api/AppInitializer.java @@ -0,0 +1,75 @@ +package net.miarma.api; + +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.security.SecretManager; +import net.miarma.api.backlib.vertx.VertxJacksonConfig; +import net.miarma.api.backlib.util.MessageUtil; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +import io.vertx.core.Launcher; + +/** + * Punto de entrada para inicializar la aplicación. + * Se encarga de: + * - Crear directorios base si no existen + * - Copiar el fichero default.properties + * - Inicializar ConfigManager y secretos + * - Configurar Jackson para Vert.x + * - Desplegar el Verticle Master + */ +public class AppInitializer { + + public static void main(String[] args) { + AppInitializer initializer = new AppInitializer(); + initializer.init(); + initializer.deployMaster(); + Constants.LOGGER.info("✅ App initialized successfully!"); + } + + private final ConfigManager configManager; + + public AppInitializer() { + this.configManager = ConfigManager.getInstance(); + } + + public void init() { + initializeDirectories(); + copyDefaultConfig(); + configManager.loadConfig(); + SecretManager.getOrCreateSecret(); + VertxJacksonConfig.configure(); + } + + private void initializeDirectories() { + File baseDir = new File(configManager.getBaseDir()); + if (!baseDir.exists() && baseDir.mkdirs()) { + Constants.LOGGER.info("Created base directory: " + baseDir.getAbsolutePath()); + } + } + + private void copyDefaultConfig() { + File configFile = configManager.getConfigFile(); + if (!configFile.exists()) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream("default.properties")) { + if (in != null) { + Files.copy(in, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Constants.LOGGER.info("Copied default.properties to: " + configFile.getAbsolutePath()); + } else { + Constants.LOGGER.error(MessageUtil.notFound("Default config", "resources")); + } + } catch (IOException e) { + Constants.LOGGER.error(MessageUtil.failedTo("copy", "default config", e)); + } + } + } + + private void deployMaster() { + Launcher.executeCommand("run", MasterVerticle.class.getName()); + } +} diff --git a/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java b/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java new file mode 100644 index 0000000..8a02d5d --- /dev/null +++ b/bootstrap/src/main/java/net/miarma/api/MasterVerticle.java @@ -0,0 +1,66 @@ +package net.miarma.api; + +import java.util.Set; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.LogAccumulator; +import net.miarma.api.backlib.util.DeploymentUtil; +import net.miarma.api.microservices.core.verticles.CoreMainVerticle; +import net.miarma.api.microservices.huertos.verticles.HuertosMainVerticle; +import net.miarma.api.microservices.huertosdecine.verticles.CineMainVerticle; +import net.miarma.api.microservices.miarmacraft.verticles.MMCMainVerticle; +import net.miarma.api.microservices.mpaste.verticles.MPasteMainVerticle; + +public class MasterVerticle extends AbstractVerticle { + @Override + public void start(Promise startPromise) { + deploy() + .onSuccess(v -> { + vertx.setTimer(300, id -> { + LogAccumulator.flushToLogger(Constants.LOGGER); + startPromise.complete(); + }); + }) + .onFailure(startPromise::fail); + } + + private Future deploy() { + Promise promise = Promise.promise(); + + Future core = vertx.deployVerticle(new CoreMainVerticle()) + .onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(CoreMainVerticle.class))) + .onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(CoreMainVerticle.class, err))); + + Future huertos = vertx.deployVerticle(new HuertosMainVerticle()) + .onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(HuertosMainVerticle.class))) + .onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(HuertosMainVerticle.class, err))); + + Future mmc = vertx.deployVerticle(new MMCMainVerticle()) + .onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(MMCMainVerticle.class))) + .onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(MMCMainVerticle.class, err))); + + Future cine = vertx.deployVerticle(new CineMainVerticle()) + .onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(CineMainVerticle.class))) + .onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(CineMainVerticle.class, err))); + + Future mpaste = vertx.deployVerticle(new MPasteMainVerticle()) + .onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(MPasteMainVerticle.class))) + .onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(MPasteMainVerticle.class, err))); + + Future.all(core, huertos, mmc, cine, mpaste) + .onSuccess(_ -> promise.complete()) + .onFailure(promise::fail); + + return promise.future(); + } + + + @Override + public void stop(Promise stopPromise) { + vertx.deploymentIDs().forEach(id -> vertx.undeploy(id)); + stopPromise.complete(); + } +} diff --git a/bootstrap/src/main/resources/default.properties b/bootstrap/src/main/resources/default.properties new file mode 100644 index 0000000..e75ab2e --- /dev/null +++ b/bootstrap/src/main/resources/default.properties @@ -0,0 +1,41 @@ +# DB Configuration +db.protocol=jdbc:mariadb +db.host=localhost +db.port=3306 +db.name=miarma +db.user=root +db.password=root +dp.poolSize=5 + +# HTTP Server Configuration +inet.host=localhost +sso.logic.port=8080 +sso.data.port=8081 +mmc.logic.port=8100 +mmc.data.port=8101 +huertos.logic.port=8120 +huertos.data.port=8121 +cine.data.port = 8140 +cine.logic.port = 8141 +mpaste.data.port = 8160 +mpaste.logic.port = 8161 + +# Security Configuration +jwt.secret= +jwt.expiration=604800 +jwt.expiration.short=3600 + +# Mail Configuration +smtp.server= +smtp.port= +imap.server= +imap.port= + +smtp.password.presidente= +smtp.password.secretaria= +smtp.password.tesoreria= +smtp.password.admin= +smtp.password.noreply= + +# Discord Configuration +discord.webhook= \ No newline at end of file diff --git a/bootstrap/src/main/resources/logback.xml b/bootstrap/src/main/resources/logback.xml new file mode 100644 index 0000000..a7b88d9 --- /dev/null +++ b/bootstrap/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + %cyan([%d{HH:mm:ss}]) %highlight(%-5level) %green(%logger{20}) - %msg%n + + + + + + + + + + + + + + diff --git a/microservices/core/.gitignore b/microservices/core/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/microservices/core/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/microservices/core/pom.xml b/microservices/core/pom.xml new file mode 100644 index 0000000..a4d4c2f --- /dev/null +++ b/microservices/core/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + net.miarma.api + core + 1.2.0 + + + 23 + 23 + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + net.miarma.api + backlib + 1.2.0 + + + + + ME-Core + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.api.microservices.core.verticles.CoreMainVerticle + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000..114ca94 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreDataRouter.java @@ -0,0 +1,35 @@ +package net.miarma.api.microservices.core.routing; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants.CoreUserRole; +import net.miarma.api.backlib.core.handlers.FileDataHandler; +import net.miarma.api.backlib.core.handlers.UserDataHandler; +import net.miarma.api.backlib.core.services.UserService; +import net.miarma.api.microservices.core.routing.middlewares.CoreAuthGuard; + +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()); + + 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); + + } +} 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 new file mode 100644 index 0000000..d8e5ba2 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreEndpoints.java @@ -0,0 +1,39 @@ +package net.miarma.api.microservices.core.routing; + +import net.miarma.api.backlib.Constants; + +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 +} 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 new file mode 100644 index 0000000..c62a53a --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/CoreLogicRouter.java @@ -0,0 +1,44 @@ +package net.miarma.api.microservices.core.routing; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants.CoreUserRole; +import net.miarma.api.backlib.core.handlers.FileLogicHandler; +import net.miarma.api.backlib.core.handlers.ScreenshotHandler; +import net.miarma.api.backlib.core.handlers.UserLogicHandler; +import net.miarma.api.backlib.core.services.UserService; +import net.miarma.api.microservices.core.routing.middlewares.CoreAuthGuard; + +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); + 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.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); + } +} 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 new file mode 100644 index 0000000..0d8ee21 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/routing/middlewares/CoreAuthGuard.java @@ -0,0 +1,35 @@ +package net.miarma.api.microservices.core.routing.middlewares; + +import java.util.function.Consumer; + +import io.vertx.ext.web.RoutingContext; +import net.miarma.api.backlib.Constants.CoreUserRole; +import net.miarma.api.backlib.middlewares.AbstractAuthGuard; +import net.miarma.api.backlib.core.entities.UserEntity; +import net.miarma.api.backlib.core.services.UserService; + +public class CoreAuthGuard extends AbstractAuthGuard { + private final UserService userService; + + public CoreAuthGuard(UserService userService) { + this.userService = userService; + } + + @Override + protected CoreUserRole parseRole(String roleStr) { + return CoreUserRole.valueOf(roleStr.toUpperCase()); + } + + @Override + protected void getUserEntity(int userId, RoutingContext ctx, Consumer callback) { + userService.getById(userId).onComplete(ar -> { + if (ar.succeeded()) callback.accept(ar.result()); + else callback.accept(null); + }); + } + + @Override + protected boolean hasPermission(UserEntity user, CoreUserRole role) { + return user.getGlobal_role() == CoreUserRole.ADMIN; + } +} 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 new file mode 100644 index 0000000..f5041e0 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreDataVerticle.java @@ -0,0 +1,197 @@ +package net.miarma.api.microservices.core.verticles; + +import java.util.HashMap; +import java.util.Map; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.Constants.CoreUserGlobalStatus; +import net.miarma.api.backlib.Constants.CoreUserRole; +import net.miarma.api.backlib.core.entities.UserEntity; +import net.miarma.api.backlib.core.services.FileService; +import net.miarma.api.backlib.core.services.UserService; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.EventBusUtil; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.core.routing.CoreDataRouter; + +@SuppressWarnings("unused") +public class CoreDataVerticle extends AbstractVerticle { + private ConfigManager configManager; + private UserService userService; + private FileService fileService; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + userService = new UserService(pool); + fileService = new FileService(pool); + 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 -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } + + private void registerLogicVerticleConsumer() { + vertx.eventBus().consumer(Constants.AUTH_EVENT_BUS, 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)); + } + + 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); + } + }); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..e6a1603 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreLogicVerticle.java @@ -0,0 +1,32 @@ +package net.miarma.api.microservices.core.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.core.routing.CoreLogicRouter; + +public class CoreLogicVerticle extends AbstractVerticle { + ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + CoreLogicRouter.mount(router, vertx, pool); + + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("sso.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 new file mode 100644 index 0000000..a4a13f8 --- /dev/null +++ b/microservices/core/src/main/java/net/miarma/api/microservices/core/verticles/CoreMainVerticle.java @@ -0,0 +1,62 @@ +package net.miarma.api.microservices.core.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Promise; +import io.vertx.core.ThreadingModel; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.LogAccumulator; +import net.miarma.api.backlib.util.DeploymentUtil; + +public class CoreMainVerticle extends AbstractVerticle { + + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + try { + this.configManager = ConfigManager.getInstance(); + deployVerticles(); + startPromise.complete(); + } catch (Exception e) { + Constants.LOGGER.error(DeploymentUtil.failMessage(CoreMainVerticle.class, e)); + startPromise.fail(e); + } + } + + private void deployVerticles() { + final DeploymentOptions options = new DeploymentOptions() + .setThreadingModel(ThreadingModel.WORKER); + + vertx.deployVerticle(new CoreDataVerticle(), options, result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(CoreDataVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("sso.data.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(CoreDataVerticle.class, result.cause())); + } + }); + + vertx.deployVerticle(new CoreLogicVerticle(), options, result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(CoreLogicVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("sso.logic.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(CoreLogicVerticle.class, result.cause())); + } + }); + } +} diff --git a/microservices/huertos/.gitignore b/microservices/huertos/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/microservices/huertos/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/microservices/huertos/pom.xml b/microservices/huertos/pom.xml new file mode 100644 index 0000000..de60f98 --- /dev/null +++ b/microservices/huertos/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + net.miarma.api + huertos + 1.2.0 + + + 23 + 23 + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + net.miarma.api + backlib + 1.2.0 + + + + + ME-Huertos + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.api.microservices.huertos.HuertosMainVerticle + + + + + + + + + + diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/AnnouncementDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/AnnouncementDAO.java new file mode 100644 index 0000000..92da3db --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/AnnouncementDAO.java @@ -0,0 +1,133 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.AnnouncementEntity; + +import java.util.List; +import java.util.Map; + +public class AnnouncementDAO implements DataAccessObject { + + private final DatabaseManager db; + + public AnnouncementDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(AnnouncementEntity.class) + .where(Map.of("announce_id", id.toString())) + .build(); + + db.executeOne(query, AnnouncementEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(AnnouncementEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, AnnouncementEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(AnnouncementEntity announce) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(announce).build(); + + db.execute(query, AnnouncementEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(AnnouncementEntity announcementEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(announcementEntity, conflictKeys).build(); + + db.executeOne(query, AnnouncementEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(AnnouncementEntity announce) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(announce).build(); + + db.executeOne(query, AnnouncementEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + AnnouncementEntity announce = new AnnouncementEntity(); + announce.setAnnounce_id(id); + + String query = QueryBuilder.delete(announce).build(); + + db.executeOne(query, AnnouncementEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(AnnouncementEntity.class) + .where(Map.of("announce_id", id.toString())) + .build(); + + db.executeOne(query, AnnouncementEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/BalanceDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/BalanceDAO.java new file mode 100644 index 0000000..f8bb592 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/BalanceDAO.java @@ -0,0 +1,134 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.BalanceEntity; +import net.miarma.api.microservices.huertos.entities.ViewBalanceWithTotals; + +import java.util.List; +import java.util.Map; + +public class BalanceDAO implements DataAccessObject { + + private final DatabaseManager db; + + public BalanceDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + Promise> promise = Promise.promise(); + String query = QueryBuilder.select(BalanceEntity.class).build(); + + db.execute(query, BalanceEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(BalanceEntity.class) + .where(Map.of("id", id.toString())) + .build(); + + db.executeOne(query, BalanceEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAllWithTotals() { + Promise> promise = Promise.promise(); + String query = QueryBuilder.select(ViewBalanceWithTotals.class).build(); + + db.execute(query, ViewBalanceWithTotals.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(BalanceEntity balance) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(balance).build(); + + db.execute(query, BalanceEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(BalanceEntity balanceEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(balanceEntity, conflictKeys).build(); + + db.executeOne(query, BalanceEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(BalanceEntity balance) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(balance).build(); + + db.executeOne(query, BalanceEntity.class, + _ -> promise.complete(balance), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + BalanceEntity balance = new BalanceEntity(); + balance.setId(id); + + String query = QueryBuilder.delete(balance).build(); + + db.executeOne(query, BalanceEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(BalanceEntity.class) + .where(Map.of("id", id.toString())) + .build(); + + db.executeOne(query, BalanceEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/ExpenseDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/ExpenseDAO.java new file mode 100644 index 0000000..3d34a51 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/ExpenseDAO.java @@ -0,0 +1,133 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.ExpenseEntity; + +import java.util.List; +import java.util.Map; + +public class ExpenseDAO implements DataAccessObject { + + private final DatabaseManager db; + + public ExpenseDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ExpenseEntity.class) + .where(Map.of("expense_id", id.toString())) + .build(); + + db.executeOne(query, ExpenseEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(ExpenseEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, ExpenseEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(ExpenseEntity expense) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(expense).build(); + + db.execute(query, ExpenseEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(ExpenseEntity expenseEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(expenseEntity, conflictKeys).build(); + + db.executeOne(query, ExpenseEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(ExpenseEntity expense) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(expense).build(); + + db.executeOne(query, ExpenseEntity.class, + _ -> promise.complete(expense), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + ExpenseEntity expense = new ExpenseEntity(); + expense.setExpense_id(id); + + String query = QueryBuilder.delete(expense).build(); + + db.executeOne(query, ExpenseEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ExpenseEntity.class) + .where(Map.of("expense_id", id.toString())) + .build(); + + db.executeOne(query, ExpenseEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/IncomeDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/IncomeDAO.java new file mode 100644 index 0000000..8346544 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/IncomeDAO.java @@ -0,0 +1,171 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.IncomeEntity; +import net.miarma.api.microservices.huertos.entities.ViewIncomesWithFullNames; + +import java.util.List; +import java.util.Map; + +public class IncomeDAO implements DataAccessObject { + + private final DatabaseManager db; + + public IncomeDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(IncomeEntity.class) + .where(Map.of("income_id", id.toString())) + .build(); + + db.executeOne(query, IncomeEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(IncomeEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, IncomeEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + public Future> getAllWithNames() { + return getAllWithNames(new QueryParams(Map.of(), new QueryFilters())); + } + + public Future> getAllWithNames(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(ViewIncomesWithFullNames.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, ViewIncomesWithFullNames.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + public Future> getUserIncomes(Integer memberNumber) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(IncomeEntity.class) + .where(Map.of("member_number", memberNumber.toString())) + .build(); + + db.execute(query, IncomeEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(IncomeEntity income) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(income).build(); + + db.execute(query, IncomeEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(IncomeEntity incomeEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(incomeEntity, conflictKeys).build(); + + db.executeOne(query, IncomeEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(IncomeEntity income) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(income).build(); + + db.executeOne(query, IncomeEntity.class, + _ -> promise.complete(income), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + IncomeEntity income = new IncomeEntity(); + income.setIncome_id(id); + + String query = QueryBuilder.delete(income).build(); + + db.executeOne(query, IncomeEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(IncomeEntity.class) + .where(Map.of("income_id", id.toString())) + .build(); + + db.executeOne(query, IncomeEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/MemberDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/MemberDAO.java new file mode 100644 index 0000000..4f828c4 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/MemberDAO.java @@ -0,0 +1,235 @@ +package net.miarma.api.microservices.huertos.dao; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +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.microservices.huertos.entities.MemberEntity; + +public class MemberDAO implements DataAccessObject { + + private final DatabaseManager db; + + public MemberDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + db.execute(query, MemberEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + return promise.future(); + } + + public Future getByMemberNumber(Integer memberNumber) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("member_number", memberNumber.toString())) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future getByPlotNumber(Integer plotNumber) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("plot_number", plotNumber.toString())) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future getByEmail(String email) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("email", email)) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future getByDni(String dni) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("dni", dni)) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future getByPhone(Integer phone) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("phone", phone.toString())) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getWaitlist() { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("type", "0", "status", String.valueOf(Constants.HuertosUserStatus.ACTIVE.getValue()))) + .build(); + + db.execute(query, MemberEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + public Future getLastMemberNumber() { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class, "member_number") + .orderBy(Optional.of("member_number"), Optional.of("DESC")) + .limit(Optional.of(1)) + .build(); + + db.executeOne(query, MemberEntity.class, + result -> promise.complete(result != null ? result.getMember_number() : 0), + promise::fail + ); + + return promise.future(); + } + + public Future hasCollaborator(Integer plotNumber) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("plot_number", plotNumber.toString(), "type", String.valueOf(Constants.HuertosUserType.COLLABORATOR.getValue()))) + .build(); + + db.executeOne(query, MemberEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + public Future getCollaborator(Integer plotNumber) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("plot_number", plotNumber.toString(), "type", String.valueOf(Constants.HuertosUserType.COLLABORATOR.getValue()))) + .build(); + + db.executeOne(query, MemberEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(MemberEntity user) { + throw new UnsupportedOperationException("Insert not supported on view-based DAO"); + } + + @Override + public Future upsert(MemberEntity memberEntity, String... conflictKeys) { + throw new UnsupportedOperationException("Upsert not supported on view-based DAO"); + } + + @Override + public Future update(MemberEntity user) { + throw new UnsupportedOperationException("Update not supported on view-based DAO"); + } + + @Override + public Future delete(Integer id) { + throw new UnsupportedOperationException("Delete not supported on view-based DAO"); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MemberEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, MemberEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/PreUserDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/PreUserDAO.java new file mode 100644 index 0000000..3cf7c2c --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/PreUserDAO.java @@ -0,0 +1,148 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.PreUserEntity; + +import java.util.List; +import java.util.Map; + +public class PreUserDAO implements DataAccessObject { + + private final DatabaseManager db; + + public PreUserDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PreUserEntity.class) + .where(Map.of("pre_user_id", id.toString())) + .build(); + + db.executeOne(query, PreUserEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future getByRequestId(Integer requestId) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PreUserEntity.class) + .where(Map.of("request_id", requestId.toString())) + .build(); + + db.executeOne(query, PreUserEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(PreUserEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, PreUserEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(PreUserEntity preUser) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(preUser).build(); + + db.execute(query, PreUserEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(PreUserEntity preUserEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(preUserEntity, conflictKeys).build(); + + db.execute(query, PreUserEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(PreUserEntity preUser) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(preUser).build(); + + db.executeOne(query, PreUserEntity.class, + _ -> promise.complete(preUser), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + PreUserEntity preUser = new PreUserEntity(); + preUser.setPre_user_id(id); + + String query = QueryBuilder.delete(preUser).build(); + + db.executeOne(query, PreUserEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PreUserEntity.class) + .where(Map.of("pre_user_id", id.toString())) + .build(); + + db.execute(query, PreUserEntity.class, + list -> promise.complete(!list.isEmpty()), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/RequestDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/RequestDAO.java new file mode 100644 index 0000000..997b3d4 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/RequestDAO.java @@ -0,0 +1,176 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.RequestEntity; +import net.miarma.api.microservices.huertos.entities.ViewRequestsWithPreUsers; + +import java.util.List; +import java.util.Map; + +public class RequestDAO implements DataAccessObject { + + private final DatabaseManager db; + + public RequestDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(RequestEntity.class) + .where(Map.of("request_id", id.toString())) + .build(); + + db.executeOne(query, RequestEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(RequestEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, RequestEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + public Future> getRequestsWithPreUsers() { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(ViewRequestsWithPreUsers.class) + .build(); + + db.execute(query, ViewRequestsWithPreUsers.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + public Future getRequestWithPreUserById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ViewRequestsWithPreUsers.class) + .where(Map.of("request_id", id.toString())) + .build(); + db.executeOne(query, ViewRequestsWithPreUsers.class, + promise::complete, + promise::fail + ); + return promise.future(); + } + + public Future> getByUserId(Integer userId) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(RequestEntity.class) + .where(Map.of("requested_by", userId.toString())) + .build(); + + db.execute(query, RequestEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(RequestEntity request) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(request).build(); + + db.execute(query, RequestEntity.class, + list -> promise.complete(list.isEmpty() ? null : list.getFirst()), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(RequestEntity requestEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(requestEntity, conflictKeys).build(); + + db.executeOne(query, RequestEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(RequestEntity request) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(request).build(); + + db.executeOne(query, RequestEntity.class, + _ -> promise.complete(request), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + RequestEntity request = new RequestEntity(); + request.setRequest_id(id); + + String query = QueryBuilder.delete(request).build(); + + db.executeOne(query, RequestEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(RequestEntity.class) + .where(Map.of("request_id", id.toString())) + .build(); + + db.executeOne(query, RequestEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/UserMetadataDAO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/UserMetadataDAO.java new file mode 100644 index 0000000..dae8b6c --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/dao/UserMetadataDAO.java @@ -0,0 +1,146 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.UserMetadataEntity; + +import java.util.List; +import java.util.Map; + +public class UserMetadataDAO implements DataAccessObject { + + private final DatabaseManager db; + + public UserMetadataDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, UserMetadataEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(UserMetadataEntity user) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(user).build(); + + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(UserMetadataEntity userMetadataEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(userMetadataEntity, conflictKeys).build(); + + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(UserMetadataEntity user) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(user).build(); + + db.executeOne(query, UserMetadataEntity.class, + _ -> promise.complete(user), + promise::fail + ); + + return promise.future(); + } + + public Future updateWithNulls(UserMetadataEntity user) { + Promise promise = Promise.promise(); + String query = QueryBuilder.updateWithNulls(user).build(); + + db.executeOne(query, UserMetadataEntity.class, + _ -> promise.complete(user), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + UserMetadataEntity user = new UserMetadataEntity(); + user.setUser_id(id); + + String query = QueryBuilder.delete(user).build(); + + db.executeOne(query, UserMetadataEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/AnnouncementEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/AnnouncementEntity.java new file mode 100644 index 0000000..1b3d874 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/AnnouncementEntity.java @@ -0,0 +1,58 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosAnnouncePriority; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("huertos_announces") +public class AnnouncementEntity extends AbstractEntity { + private Integer announce_id; + private String body; + private HuertosAnnouncePriority priority; + private Integer published_by; + private LocalDateTime created_at; + + public AnnouncementEntity() { + super(); + } + + public AnnouncementEntity(Row row) { + super(row); + } + + public Integer getAnnounce_id() { + return announce_id; + } + public void setAnnounce_id(Integer announce_id) { + this.announce_id = announce_id; + } + public String getBody() { + return body; + } + public void setBody(String body) { + this.body = body; + } + public HuertosAnnouncePriority getPriority() { + return priority; + } + public void setPriority(HuertosAnnouncePriority priority) { + this.priority = priority; + } + public Integer getPublished_by() { + return published_by; + } + public void setPublished_by(Integer published_by) { + this.published_by = published_by; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/BalanceEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/BalanceEntity.java new file mode 100644 index 0000000..5d26b92 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/BalanceEntity.java @@ -0,0 +1,51 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Table("huertos_balance") +public class BalanceEntity extends AbstractEntity { + private Integer id; + private BigDecimal initial_bank; + private BigDecimal initial_cash; + private LocalDateTime created_at; + + public BalanceEntity() { + super(); + } + + public BalanceEntity(Row row) { + super(row); + } + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public BigDecimal getInitial_bank() { + return initial_bank; + } + public void setInitial_bank(BigDecimal initial_bank) { + this.initial_bank = initial_bank; + } + public BigDecimal getInitial_cash() { + return initial_cash; + } + public void setInitial_cash(BigDecimal initial_cash) { + this.initial_cash = initial_cash; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ExpenseEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ExpenseEntity.java new file mode 100644 index 0000000..08a1fd4 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ExpenseEntity.java @@ -0,0 +1,73 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosPaymentType; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Table("huertos_expenses") +public class ExpenseEntity extends AbstractEntity{ + private Integer expense_id; + private String concept; + private BigDecimal amount; + private String supplier; + private String invoice; + private HuertosPaymentType type; + private LocalDateTime created_at; + + public ExpenseEntity() { + super(); + } + + public ExpenseEntity(Row row) { + super(row); + } + + public Integer getExpense_id() { + return expense_id; + } + public void setExpense_id(Integer expense_id) { + this.expense_id = expense_id; + } + public String getConcept() { + return concept; + } + public void setConcept(String concept) { + this.concept = concept; + } + public BigDecimal getAmount() { + return amount; + } + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + public String getSupplier() { + return supplier; + } + public void setSupplier(String supplier) { + this.supplier = supplier; + } + public String getInvoice() { + return invoice; + } + public void setInvoice(String invoice) { + this.invoice = invoice; + } + public HuertosPaymentType getType() { + return type; + } + public void setType(HuertosPaymentType type) { + this.type = type; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/IncomeEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/IncomeEntity.java new file mode 100644 index 0000000..050d76f --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/IncomeEntity.java @@ -0,0 +1,85 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosPaymentFrequency; +import net.miarma.api.backlib.Constants.HuertosPaymentType; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +@Table("huertos_incomes") +public class IncomeEntity extends AbstractEntity { + private Integer income_id; + private Integer member_number; + private String concept; + private BigDecimal amount; + private HuertosPaymentType type; + private HuertosPaymentFrequency frequency; + private LocalDateTime created_at; + + public IncomeEntity() { + super(); + } + + public IncomeEntity(Row row) { + super(row); + } + + public Integer getIncome_id() { + return income_id; + } + public void setIncome_id(Integer income_id) { + this.income_id = income_id; + } + public Integer getMember_number() { + return member_number; + } + public void setMember_number(Integer member_number) { + this.member_number = member_number; + } + public String getConcept() { + return concept; + } + public void setConcept(String concept) { + this.concept = concept; + } + public BigDecimal getAmount() { + return amount; + } + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + public HuertosPaymentType getType() { + return type; + } + public void setType(HuertosPaymentType type) { + this.type = type; + } + public HuertosPaymentFrequency getFrequency() { + return frequency; + } + public void setFrequency(HuertosPaymentFrequency frequency) { + this.frequency = frequency; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + public boolean isPaid() { + if(frequency == HuertosPaymentFrequency.BIYEARLY) { + return (System.currentTimeMillis() - created_at.toEpochSecond(ZoneOffset.UTC)) > 6L * 30 * 24 * 60 * 60 * 1000; + } else if(frequency == HuertosPaymentFrequency.YEARLY) { + return (System.currentTimeMillis() - created_at.toEpochSecond(ZoneOffset.UTC)) > 12L * 30 * 24 * 60 * 60 * 1000; + } else { + return false; + } + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/MemberEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/MemberEntity.java new file mode 100644 index 0000000..67770b5 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/MemberEntity.java @@ -0,0 +1,234 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.*; +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; + +import java.time.LocalDateTime; + +@Table("v_huertos_members") +public class MemberEntity extends AbstractEntity implements IUser { + private Integer user_id; + private Integer member_number; + private Integer plot_number; + private String display_name; + private String dni; + private Integer phone; + private String email; + private String user_name; + @APIDontReturn + private String password; + private String avatar; + private LocalDateTime created_at; + private LocalDateTime assigned_at; + private LocalDateTime deactivated_at; + private String notes; + private HuertosUserType type; + private HuertosUserStatus status; + private HuertosUserRole role; + private CoreUserGlobalStatus global_status; + private CoreUserRole global_role; + + public MemberEntity() { + super(); + } + + public MemberEntity(Row row) { + super(row); + } + + public MemberEntity(IUser user, UserMetadataEntity metadata) { + this.user_id = user.getUser_id(); + this.member_number = metadata.getMember_number(); + this.plot_number = metadata.getPlot_number(); + this.display_name = user.getDisplay_name(); + this.dni = metadata.getDni(); + this.phone = metadata.getPhone(); + this.email = user.getEmail(); + this.user_name = user.getUser_name(); + this.password = user.getPassword(); + this.avatar = user.getAvatar(); + this.created_at = metadata.getCreated_at(); + this.assigned_at = metadata.getAssigned_at(); + this.deactivated_at = metadata.getDeactivated_at(); + this.notes = metadata.getNotes(); + this.type = metadata.getType(); + this.status = metadata.getStatus(); + this.role = metadata.getRole(); + this.global_status = user.getGlobal_status(); + this.global_role = user.getGlobal_role(); + } + + public Integer getUser_id() { + return user_id; + } + + public void setUser_id(Integer user_id) { + this.user_id = user_id; + } + + public Integer getMember_number() { + return member_number; + } + + public void setMember_number(Integer member_number) { + this.member_number = member_number; + } + + public Integer getPlot_number() { + return plot_number; + } + + public void setPlot_number(Integer plot_number) { + this.plot_number = plot_number; + } + + public String getDisplay_name() { + return display_name; + } + + public void setDisplay_name(String display_name) { + this.display_name = display_name; + } + + public String getDni() { + return dni; + } + + public void setDni(String dni) { + this.dni = dni; + } + + public Integer getPhone() { + return phone; + } + + public void setPhone(Integer phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUser_name() { + return user_name; + } + + public void setUser_name(String user_name) { + this.user_name = user_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 LocalDateTime getCreated_at() { + return created_at; + } + + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + public LocalDateTime getAssigned_at() { + return assigned_at; + } + + public void setAssigned_at(LocalDateTime assigned_at) { + this.assigned_at = assigned_at; + } + + public LocalDateTime getDeactivated_at() { + return deactivated_at; + } + + public void setDeactivated_at(LocalDateTime deactivated_at) { + this.deactivated_at = deactivated_at; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public HuertosUserType getType() { + return type; + } + + public void setType(HuertosUserType type) { + this.type = type; + } + + public HuertosUserStatus getStatus() { + return status; + } + + public void setStatus(HuertosUserStatus status) { + this.status = status; + } + + public HuertosUserRole getRole() { + return role; + } + + public void setRole(HuertosUserRole role) { + this.role = role; + } + + public CoreUserGlobalStatus getGlobal_status() { + return global_status; + } + + public void setGlobal_status(CoreUserGlobalStatus global_status) { + this.global_status = global_status; + } + + public CoreUserRole getGlobal_role() { + return global_role; + } + + public void setGlobal_role(CoreUserRole global_role) { + this.global_role = global_role; + } + + public static MemberEntity fromPreUser(PreUserEntity preUser) { + MemberEntity member = new MemberEntity(); + member.setMember_number(preUser.getMember_number()); + member.setPlot_number(preUser.getPlot_number()); + member.setDisplay_name(preUser.getDisplay_name()); + member.setDni(preUser.getDni()); + member.setPhone(preUser.getPhone()); + member.setEmail(preUser.getEmail()); + member.setUser_name(preUser.getUser_name()); + member.setCreated_at(preUser.getCreated_at()); + member.setType(preUser.getType()); + member.setStatus(preUser.getStatus()); + member.setRole(HuertosUserRole.USER); + member.setGlobal_status(CoreUserGlobalStatus.ACTIVE); + member.setGlobal_role(CoreUserRole.USER); + return member; + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/PreUserEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/PreUserEntity.java new file mode 100644 index 0000000..bebca9f --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/PreUserEntity.java @@ -0,0 +1,135 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosUserStatus; +import net.miarma.api.backlib.Constants.HuertosUserType; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("huertos_pre_users") +public class PreUserEntity extends AbstractEntity { + private Integer pre_user_id; + private Integer request_id; + private String user_name; + private String display_name; + private String dni; + private Integer phone; + private String email; + private String password; + private String address; + private String zip_code; + private String city; + private Integer member_number; + private Integer plot_number; + private HuertosUserType type; + private HuertosUserStatus status; + private LocalDateTime created_at; + + + public PreUserEntity() { + super(); + } + + public PreUserEntity(Row row) { + super(row); + } + + public Integer getPre_user_id() { + return pre_user_id; + } + public void setPre_user_id(Integer pre_user_id) { + this.pre_user_id = pre_user_id; + } + public Integer getRequest_id() { + return request_id; + } + public void setRequest_id(Integer request_id) { + this.request_id = request_id; + } + public String getUser_name() { + return user_name; + } + public void setUser_name(String user_name) { + this.user_name = user_name; + } + public String getDisplay_name() { + return display_name; + } + public void setDisplay_name(String display_name) { + this.display_name = display_name; + } + public String getDni() { + return dni; + } + public void setDni(String dni) { + this.dni = dni; + } + public Integer getPhone() { + return phone; + } + public void setPhone(Integer phone) { + this.phone = phone; + } + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public String getAddress() { + return address; + } + public void setAddress(String address) { + this.address = address; + } + public String getZip_code() { + return zip_code; + } + public void setZip_code(String zip_code) { + this.zip_code = zip_code; + } + public String getCity() { + return city; + } + public void setCity(String city) { + this.city = city; + } + public Integer getMember_number() { + return member_number; + } + public void setMember_number(Integer member_number) { + this.member_number = member_number; + } + public Integer getPlot_number() { + return plot_number; + } + public void setPlot_number(Integer plot_number) { + this.plot_number = plot_number; + } + public HuertosUserType getType() { + return type; + } + public void setType(HuertosUserType type) { + this.type = type; + } + public HuertosUserStatus getStatus() { + return status; + } + public void setStatus(HuertosUserStatus status) { + this.status = status; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ProfileDTO.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ProfileDTO.java new file mode 100644 index 0000000..e68566a --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ProfileDTO.java @@ -0,0 +1,57 @@ +package net.miarma.api.microservices.huertos.entities; + +import java.util.List; + +public class ProfileDTO { + private MemberEntity member; + private List requests; + private List payments; + private boolean hasCollaborator; + private boolean hasCollaboratorRequest; + private boolean hasGreenHouse; + private boolean hasGreenHouseRequest; + public MemberEntity getMember() { + return member; + } + public void setMember(MemberEntity member) { + this.member = member; + } + public List getRequests() { + return requests; + } + public void setRequests(List requests) { + this.requests = requests; + } + public List getPayments() { + return payments; + } + public void setPayments(List payments) { + this.payments = payments; + } + public boolean isHasCollaborator() { + return hasCollaborator; + } + public void setHasCollaborator(boolean hasCollaborator) { + this.hasCollaborator = hasCollaborator; + } + public boolean isHasCollaboratorRequest() { + return hasCollaboratorRequest; + } + public void setHasCollaboratorRequest(boolean hasCollaboratorRequest) { + this.hasCollaboratorRequest = hasCollaboratorRequest; + } + public boolean isHasGreenHouse() { + return hasGreenHouse; + } + public void setHasGreenHouse(boolean hasGreenHouse) { + this.hasGreenHouse = hasGreenHouse; + } + public boolean getHasGreenHouseRequest() { + return hasGreenHouseRequest; + } + public void setHasGreenHouseRequest(boolean hasGreenHouseRquest) { + this.hasGreenHouseRequest = hasGreenHouseRquest; + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/RequestEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/RequestEntity.java new file mode 100644 index 0000000..7ef8fb9 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/RequestEntity.java @@ -0,0 +1,66 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosRequestStatus; +import net.miarma.api.backlib.Constants.HuertosRequestType; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("huertos_requests") +public class RequestEntity extends AbstractEntity { + private Integer request_id; + private HuertosRequestType type; + private HuertosRequestStatus status; + private Integer requested_by; + private Integer target_user_id; + private LocalDateTime created_at; + + public RequestEntity() { + super(); + } + + public RequestEntity(Row row) { + super(row); + } + + public Integer getRequest_id() { + return request_id; + } + public void setRequest_id(Integer request_id) { + this.request_id = request_id; + } + public HuertosRequestType getType() { + return type; + } + public void setType(HuertosRequestType type) { + this.type = type; + } + public HuertosRequestStatus getStatus() { + return status; + } + public void setStatus(HuertosRequestStatus status) { + this.status = status; + } + public Integer getRequested_by() { + return requested_by; + } + public void setRequested_by(Integer requested_by) { + this.requested_by = requested_by; + } + public Integer getTarget_user_id() { + return target_user_id; + } + public void setTarget_user_id(Integer target_user_id) { + this.target_user_id = target_user_id; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/UserMetadataEntity.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/UserMetadataEntity.java new file mode 100644 index 0000000..724b905 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/UserMetadataEntity.java @@ -0,0 +1,127 @@ +package net.miarma.api.microservices.huertos.entities; + +import java.time.LocalDateTime; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosUserRole; +import net.miarma.api.backlib.Constants.HuertosUserStatus; +import net.miarma.api.backlib.Constants.HuertosUserType; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +@Table("huertos_user_metadata") +public class UserMetadataEntity extends AbstractEntity { + private Integer user_id; + private Integer member_number; + private Integer plot_number; + private String dni; + private Integer phone; + private LocalDateTime created_at; + private LocalDateTime assigned_at; + private LocalDateTime deactivated_at; + private String notes; + private HuertosUserType type; + private HuertosUserStatus status; + private HuertosUserRole role; + + + public UserMetadataEntity() { + super(); + } + + public UserMetadataEntity(Row row) { + super(row); + } + + public Integer getUser_id() { + return user_id; + } + public void setUser_id(Integer user_id) { + this.user_id = user_id; + } + public Integer getMember_number() { + return member_number; + } + public void setMember_number(Integer member_number) { + this.member_number = member_number; + } + public Integer getPlot_number() { + return plot_number; + } + public void setPlot_number(Integer plot_number) { + this.plot_number = plot_number; + } + public String getDni() { + return dni; + } + public void setDni(String dni) { + this.dni = dni; + } + public Integer getPhone() { + return phone; + } + public void setPhone(Integer phone) { + this.phone = phone; + } + public LocalDateTime getCreated_at() { + return created_at; + } + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + public LocalDateTime getAssigned_at() { + return assigned_at; + } + public void setAssigned_at(LocalDateTime assigned_at) { + this.assigned_at = assigned_at; + } + public LocalDateTime getDeactivated_at() { + return deactivated_at; + } + public void setDeactivated_at(LocalDateTime deactivated_at) { + this.deactivated_at = deactivated_at; + } + public String getNotes() { + return notes; + } + public void setNotes(String notes) { + this.notes = notes; + } + public HuertosUserType getType() { + return type; + } + public void setType(HuertosUserType type) { + this.type = type; + } + public HuertosUserStatus getStatus() { + return status; + } + public void setStatus(HuertosUserStatus status) { + this.status = status; + } + public HuertosUserRole getRole() { + return role; + } + public void setRole(HuertosUserRole role) { + this.role = role; + } + + public static UserMetadataEntity fromMemberEntity(MemberEntity member) { + UserMetadataEntity meta = new UserMetadataEntity(); + meta.setUser_id(member.getUser_id()); + meta.setMember_number(member.getMember_number()); + meta.setPlot_number(member.getPlot_number()); + meta.setDni(member.getDni()); + meta.setPhone(member.getPhone()); + meta.setCreated_at(member.getCreated_at()); + meta.setAssigned_at(member.getAssigned_at()); + meta.setDeactivated_at(member.getDeactivated_at()); + meta.setNotes(member.getNotes()); + meta.setType(member.getType()); + meta.setStatus(member.getStatus()); + meta.setRole(member.getRole()); + return meta; + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewBalanceWithTotals.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewBalanceWithTotals.java new file mode 100644 index 0000000..931efe6 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewBalanceWithTotals.java @@ -0,0 +1,95 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Table("v_balance_with_totals") +public class ViewBalanceWithTotals extends AbstractEntity{ + private Integer id; + private BigDecimal initial_bank; + private BigDecimal initial_cash; + private BigDecimal total_bank_expenses; + private BigDecimal total_cash_expenses; + private BigDecimal total_bank_incomes; + private BigDecimal total_cash_incomes; + private LocalDateTime created_at; + + public ViewBalanceWithTotals() { + super(); + } + + public ViewBalanceWithTotals(Row row) { + super(row); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public BigDecimal getInitial_bank() { + return initial_bank; + } + + public void setInitial_bank(BigDecimal initial_bank) { + this.initial_bank = initial_bank; + } + + public BigDecimal getInitial_cash() { + return initial_cash; + } + + public void setInitial_cash(BigDecimal initial_cash) { + this.initial_cash = initial_cash; + } + + public BigDecimal getTotal_bank_expenses() { + return total_bank_expenses; + } + + public void setTotal_bank_expenses(BigDecimal total_bank_expenses) { + this.total_bank_expenses = total_bank_expenses; + } + + public BigDecimal getTotal_cash_expenses() { + return total_cash_expenses; + } + + public void setTotal_cash_expenses(BigDecimal total_cash_expenses) { + this.total_cash_expenses = total_cash_expenses; + } + + public BigDecimal getTotal_bank_incomes() { + return total_bank_incomes; + } + + public void setTotal_bank_incomes(BigDecimal total_bank_incomes) { + this.total_bank_incomes = total_bank_incomes; + } + + public BigDecimal getTotal_cash_incomes() { + return total_cash_incomes; + } + + public void setTotal_cash_incomes(BigDecimal total_cash_incomes) { + this.total_cash_incomes = total_cash_incomes; + } + + public LocalDateTime getCreated_at() { + return created_at; + } + + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewIncomesWithFullNames.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewIncomesWithFullNames.java new file mode 100644 index 0000000..3762f30 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewIncomesWithFullNames.java @@ -0,0 +1,47 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.HuertosPaymentFrequency; +import net.miarma.api.backlib.Constants.HuertosPaymentType; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Table("v_incomes_with_full_names") +public class ViewIncomesWithFullNames extends AbstractEntity { + private Integer income_id; + private Integer member_number; + private String display_name; + private String concept; + private BigDecimal amount; + private HuertosPaymentType type; + private HuertosPaymentFrequency frequency; + private LocalDateTime created_at; + + public ViewIncomesWithFullNames() { + super(); + } + + public ViewIncomesWithFullNames(Row row) { + super(row); + } + + public Integer getIncome_id() { return income_id; } + public void setIncome_id(Integer income_id) { this.income_id = income_id; } + public Integer getMember_number() { return member_number; } + public void setMember_number(Integer member_number) { this.member_number = member_number; } + public String getDisplay_name() { return display_name; } + public void setDisplay_name(String display_name) { this.display_name = display_name; } + public String getConcept() { return concept; } + public void setConcept(String concept) { this.concept = concept; } + public BigDecimal getAmount() { return amount; } + public void setAmount(BigDecimal amount) { this.amount = amount; } + public HuertosPaymentType getType() { return type; } + public void setType(HuertosPaymentType type) { this.type = type; } + public HuertosPaymentFrequency getFrequency() { return frequency; } + public void setFrequency(HuertosPaymentFrequency frequency) { this.frequency = frequency; } + public LocalDateTime getCreated_at() { return created_at; } + public void setCreated_at(LocalDateTime created_at) { this.created_at = created_at; } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewRequestsWithPreUsers.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewRequestsWithPreUsers.java new file mode 100644 index 0000000..56e8ce7 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/entities/ViewRequestsWithPreUsers.java @@ -0,0 +1,224 @@ +package net.miarma.api.microservices.huertos.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.*; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("v_requests_with_pre_users") +public class ViewRequestsWithPreUsers extends AbstractEntity { + + // huertos_requests + public Integer request_id; + public HuertosRequestType request_type; + public HuertosRequestStatus request_status; + public Integer requested_by; + // huertos_users + public String requested_by_name; + public Integer target_user_id; + public LocalDateTime request_created_at; + + // huertos_pre_users + public Integer pre_user_id; + public String pre_user_name; + public String pre_display_name; + public String pre_dni; + public Integer pre_phone; + public String pre_email; + public String pre_address; + public String pre_zip_code; + public String pre_city; + public Integer pre_member_number; + public Integer pre_plot_number; + public HuertosUserType pre_type; + public HuertosUserStatus pre_status; + public HuertosUserRole pre_role; + public LocalDateTime pre_created_at; + + public ViewRequestsWithPreUsers() { + super(); + } + + public ViewRequestsWithPreUsers(Row row) { + super(row); + } + + public Integer getRequest_id() { + return request_id; + } + + public void setRequest_id(Integer request_id) { + this.request_id = request_id; + } + + public HuertosRequestType getRequest_type() { + return request_type; + } + + public void setRequest_type(HuertosRequestType request_type) { + this.request_type = request_type; + } + + public HuertosRequestStatus getRequest_status() { + return request_status; + } + + public void setRequest_status(HuertosRequestStatus request_status) { + this.request_status = request_status; + } + + public Integer getRequested_by() { + return requested_by; + } + + public void setRequested_by(Integer requested_by) { + this.requested_by = requested_by; + } + + public String getRequested_by_name() { + return requested_by_name; + } + + public void setRequested_by_name(String requested_by_name) { + this.requested_by_name = requested_by_name; + } + + public Integer getTarget_user_id() { + return target_user_id; + } + + public void setTarget_user_id(Integer target_user_id) { + this.target_user_id = target_user_id; + } + + public LocalDateTime getRequest_created_at() { + return request_created_at; + } + + public void setRequest_created_at(LocalDateTime request_created_at) { + this.request_created_at = request_created_at; + } + + public Integer getPre_user_id() { + return pre_user_id; + } + + public void setPre_user_id(Integer pre_user_id) { + this.pre_user_id = pre_user_id; + } + + public String getPre_user_name() { + return pre_user_name; + } + + public void setPre_user_name(String pre_user_name) { + this.pre_user_name = pre_user_name; + } + + public String getPre_display_name() { + return pre_display_name; + } + + public void setPre_display_name(String pre_display_name) { + this.pre_display_name = pre_display_name; + } + + public String getPre_dni() { + return pre_dni; + } + + public void setPre_dni(String pre_dni) { + this.pre_dni = pre_dni; + } + + public Integer getPre_phone() { + return pre_phone; + } + + public void setPre_phone(Integer pre_phone) { + this.pre_phone = pre_phone; + } + + public String getPre_email() { + return pre_email; + } + + public void setPre_email(String pre_email) { + this.pre_email = pre_email; + } + + public String getPre_address() { + return pre_address; + } + + public void setPre_address(String pre_address) { + this.pre_address = pre_address; + } + + public String getPre_zip_code() { + return pre_zip_code; + } + + public void setPre_zip_code(String pre_zip_code) { + this.pre_zip_code = pre_zip_code; + } + + public String getPre_city() { + return pre_city; + } + + public void setPre_city(String pre_city) { + this.pre_city = pre_city; + } + + public Integer getPre_member_number() { + return pre_member_number; + } + + public void setPre_member_number(Integer pre_member_number) { + this.pre_member_number = pre_member_number; + } + + public Integer getPre_plot_number() { + return pre_plot_number; + } + + public void setPre_plot_number(Integer pre_plot_number) { + this.pre_plot_number = pre_plot_number; + } + + public HuertosUserType getPre_type() { + return pre_type; + } + + public void setPre_type(HuertosUserType pre_type) { + this.pre_type = pre_type; + } + + public HuertosUserStatus getPre_status() { + return pre_status; + } + + public void setPre_status(HuertosUserStatus pre_status) { + this.pre_status = pre_status; + } + + public HuertosUserRole getPre_role() { + return pre_role; + } + + public void setPre_role(HuertosUserRole pre_role) { + this.pre_role = pre_role; + } + + public LocalDateTime getPre_created_at() { + return pre_created_at; + } + + public void setPre_created_at(LocalDateTime pre_created_at) { + this.pre_created_at = pre_created_at; + } + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/AnnouncementDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/AnnouncementDataHandler.java new file mode 100644 index 0000000..991a8c9 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/AnnouncementDataHandler.java @@ -0,0 +1,59 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.AnnouncementEntity; +import net.miarma.api.microservices.huertos.services.AnnouncementService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class AnnouncementDataHandler { + final AnnouncementService announcementService; + + public AnnouncementDataHandler(Pool pool) { + this.announcementService = new AnnouncementService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + announcementService.getAll(params) + .onSuccess(announces -> JsonUtil.sendJson(ctx, ApiStatus.OK, announces)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer announcementId = Integer.parseInt(ctx.pathParam("announce_id")); + + announcementService.getById(announcementId) + .onSuccess(announce -> JsonUtil.sendJson(ctx, ApiStatus.OK, announce)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + AnnouncementEntity announce = Constants.GSON.fromJson(ctx.body().asString(), AnnouncementEntity.class); + + announcementService.create(announce) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + AnnouncementEntity announce = Constants.GSON.fromJson(ctx.body().asString(), AnnouncementEntity.class); + + announcementService.update(announce) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer announcementId = Integer.parseInt(ctx.pathParam("announce_id")); + + announcementService.delete(announcementId) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/AnnouncementLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/AnnouncementLogicHandler.java new file mode 100644 index 0000000..295db46 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/AnnouncementLogicHandler.java @@ -0,0 +1,5 @@ +package net.miarma.api.microservices.huertos.handlers; + +public class AnnouncementLogicHandler { + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/BalanceDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/BalanceDataHandler.java new file mode 100644 index 0000000..fcbc295 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/BalanceDataHandler.java @@ -0,0 +1,40 @@ +package net.miarma.api.microservices.huertos.handlers; + +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.microservices.huertos.entities.BalanceEntity; +import net.miarma.api.microservices.huertos.services.BalanceService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class BalanceDataHandler { + private final BalanceService balanceService; + + public BalanceDataHandler(Pool pool) { + this.balanceService = new BalanceService(pool); + } + + public void getBalance(RoutingContext ctx) { + balanceService.getBalance() + .onSuccess(balance -> JsonUtil.sendJson(ctx, ApiStatus.OK, balance)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + BalanceEntity balance = Constants.GSON.fromJson(ctx.body().asString(), BalanceEntity.class); + + balanceService.create(balance) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + BalanceEntity balance = Constants.GSON.fromJson(ctx.body().asString(), BalanceEntity.class); + + balanceService.update(balance) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/BalanceLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/BalanceLogicHandler.java new file mode 100644 index 0000000..f538e6c --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/BalanceLogicHandler.java @@ -0,0 +1,25 @@ +package net.miarma.api.microservices.huertos.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class BalanceLogicHandler { + private final Vertx vertx; + + public BalanceLogicHandler(Vertx vertx) { + this.vertx = vertx; + } + + public void getBalanceWithTotals(RoutingContext ctx) { + JsonObject request = new JsonObject().put("action", "getBalanceWithTotals"); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Balance not found"); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/ExpenseDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/ExpenseDataHandler.java new file mode 100644 index 0000000..beabbfc --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/ExpenseDataHandler.java @@ -0,0 +1,59 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.ExpenseEntity; +import net.miarma.api.microservices.huertos.services.ExpenseService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class ExpenseDataHandler { + private final ExpenseService expenseService; + + public ExpenseDataHandler(Pool pool) { + this.expenseService = new ExpenseService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + expenseService.getAll(params) + .onSuccess(expenses -> JsonUtil.sendJson(ctx, ApiStatus.OK, expenses)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer expenseId = Integer.parseInt(ctx.pathParam("expense_id")); + + expenseService.getById(expenseId) + .onSuccess(expense -> JsonUtil.sendJson(ctx, ApiStatus.OK, expense)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + ExpenseEntity expense = Constants.GSON.fromJson(ctx.body().asString(), ExpenseEntity.class); + + expenseService.create(expense) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + ExpenseEntity expense = Constants.GSON.fromJson(ctx.body().asString(), ExpenseEntity.class); + + expenseService.update(expense) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer expenseId = Integer.parseInt(ctx.pathParam("expense_id")); + + expenseService.delete(expenseId) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/ExpenseLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/ExpenseLogicHandler.java new file mode 100644 index 0000000..9204cc5 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/ExpenseLogicHandler.java @@ -0,0 +1,5 @@ +package net.miarma.api.microservices.huertos.handlers; + +public class ExpenseLogicHandler { + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/IncomeDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/IncomeDataHandler.java new file mode 100644 index 0000000..8b79e3a --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/IncomeDataHandler.java @@ -0,0 +1,67 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.IncomeEntity; +import net.miarma.api.microservices.huertos.services.IncomeService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class IncomeDataHandler { + private final IncomeService incomeService; + + public IncomeDataHandler(Pool pool) { + this.incomeService = new IncomeService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + incomeService.getAll(params) + .onSuccess(incomes -> JsonUtil.sendJson(ctx, ApiStatus.OK, incomes)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getIncomesWithNames(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + incomeService.getIncomesWithNames(params) + .onSuccess(incomes -> JsonUtil.sendJson(ctx, ApiStatus.OK, incomes)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer incomeId = Integer.parseInt(ctx.pathParam("income_id")); + + incomeService.getById(incomeId) + .onSuccess(income -> JsonUtil.sendJson(ctx, ApiStatus.OK, income)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + IncomeEntity income = Constants.GSON.fromJson(ctx.body().asString(), IncomeEntity.class); + + incomeService.create(income) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + IncomeEntity income = Constants.GSON.fromJson(ctx.body().asString(), IncomeEntity.class); + + incomeService.update(income) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer incomeId = Integer.parseInt(ctx.pathParam("income_id")); + + incomeService.delete(incomeId) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/IncomeLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/IncomeLogicHandler.java new file mode 100644 index 0000000..af0ae1a --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/IncomeLogicHandler.java @@ -0,0 +1,26 @@ +package net.miarma.api.microservices.huertos.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class IncomeLogicHandler { + private final Vertx vertx; + + public IncomeLogicHandler(Vertx vertx) { + this.vertx = vertx; + } + + public void getMyIncomes(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "getMyIncomes").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Incomes not found"); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MailHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MailHandler.java new file mode 100644 index 0000000..e9b80fc --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MailHandler.java @@ -0,0 +1,171 @@ +package net.miarma.api.microservices.huertos.handlers; + +import java.util.List; + +import io.vertx.core.AsyncResult; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.WorkerExecutor; +import io.vertx.core.json.Json; +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.security.JWTManager; +import net.miarma.api.microservices.huertos.entities.MemberEntity; +import net.miarma.api.microservices.huertos.mail.Attachment; +import net.miarma.api.microservices.huertos.mail.ImapReader; +import net.miarma.api.microservices.huertos.mail.Mail; +import net.miarma.api.microservices.huertos.mail.MailCredentials; +import net.miarma.api.microservices.huertos.mail.MailManager; +import net.miarma.api.microservices.huertos.services.MemberService; + +public class MailHandler { + private final MailManager mailManager; + private final WorkerExecutor mailExecutor; + private final MemberService memberService; + + public MailHandler(Vertx vertx, Pool pool) { + this.mailManager = new MailManager(vertx); + this.mailExecutor = vertx.createSharedWorkerExecutor("mail-worker-pool", 10); + this.memberService = new MemberService(pool); + } + + public void sendMail(RoutingContext ctx) { + Mail mail = Json.decodeValue(ctx.body().asString(), Mail.class); + List attachments = mail.getAttachments(); + + resolveMailCredentials(ctx, res -> { + if (res.failed()) { + ctx.response().setStatusCode(ApiStatus.UNAUTHORIZED.getCode()).end("Unauthorized: " + res.cause().getMessage()); + return; + } + + MailCredentials creds = res.result(); + + if (!attachments.isEmpty()) { + mailManager.sendEmailWithAttachment(mail, attachments, creds.username(), creds.password(), result -> { + if (result.succeeded()) { + ctx.response().setStatusCode(ApiStatus.OK.getCode()).end("Email has been sent successfully"); + } else { + Constants.LOGGER.error(result.cause().getMessage()); + ctx.response().setStatusCode(ApiStatus.INTERNAL_SERVER_ERROR.getCode()).end("Error sending email: " + result.cause().getMessage()); + } + }); + } else { + mailManager.sendEmail(mail, creds.username(), creds.password(), result -> { + if (result.succeeded()) { + ctx.response().setStatusCode(ApiStatus.OK.getCode()).end("Email has been sent successfully"); + } else { + Constants.LOGGER.error(result.cause().getMessage()); + ctx.response().setStatusCode(ApiStatus.INTERNAL_SERVER_ERROR.getCode()).end("Error sending email: " + result.cause().getMessage()); + } + }); + } + }); + } + + + public void getFolder(RoutingContext ctx) { + String folderParam = ctx.pathParam("folder"); + + if (folderParam == null) { + ctx.response().setStatusCode(ApiStatus.BAD_REQUEST.getCode()).end("Missing folder name"); + return; + } + + resolveMailCredentials(ctx, res -> { + if (res.failed()) { + ctx.response().setStatusCode(ApiStatus.UNAUTHORIZED.getCode()).end("Unauthorized: " + res.cause().getMessage()); + return; + } + + MailCredentials creds = res.result(); + + mailExecutor.executeBlocking(() -> + ImapReader.listInboxEmails(folderParam, creds.username(), creds.password()) + ).onSuccess(mails -> { + ctx.response().putHeader("Content-Type", "application/json").end(Json.encodePrettily(mails)); + }).onFailure(err -> { + ctx.response().setStatusCode(500).end("Error reading folder: " + err.getMessage()); + }); + }); + + } + + public void getMail(RoutingContext ctx) { + String folderParam = ctx.pathParam("folder"); + String indexParam = ctx.pathParam("index"); + + if (folderParam == null) { + ctx.response().setStatusCode(ApiStatus.BAD_REQUEST.getCode()).end("Missing folder name"); + return; + } + int index; + try { + index = Integer.parseInt(indexParam); + } catch (NumberFormatException e) { + ctx.response().setStatusCode(400).end("Index must be a number"); + return; + } + + resolveMailCredentials(ctx, res -> { + if (res.failed()) { + ctx.response().setStatusCode(ApiStatus.UNAUTHORIZED.getCode()).end("Unauthorized: " + res.cause().getMessage()); + return; + } + + MailCredentials creds = res.result(); + + mailExecutor.executeBlocking(() -> + ImapReader.getEmailByIndex(folderParam, creds.username(), creds.password(), index) + ).onSuccess(mail -> { + ctx.response().putHeader("Content-Type", "application/json").end(Json.encodePrettily(mail)); + }).onFailure(err -> { + ctx.response().setStatusCode(500).end("Error getting email: " + err.getMessage()); + }); + }); + + } + + private void resolveMailCredentials(RoutingContext ctx, Handler> handler) { + String token = ctx.request().getHeader("Authorization"); + + if (token == null || !token.startsWith("Bearer ")) { + handler.handle(Future.failedFuture("Missing or invalid Authorization header")); + return; + } + + int userId = JWTManager.getInstance().extractUserId(token.replace("Bearer ", "")); + + memberService.getById(userId).onComplete(ar -> { + if (ar.failed()) { + handler.handle(Future.failedFuture(ar.cause())); + return; + } + + MemberEntity member = ar.result(); + String email = member.getEmail(); + + if (email == null || !email.contains("@")) { + handler.handle(Future.failedFuture("Invalid member email")); + return; + } + + String role = email.split("@")[0]; + String password = ConfigManager.getInstance().getStringProperty("smtp.password." + role); + + if (password == null) { + handler.handle(Future.failedFuture("No password found for user")); + return; + } + + handler.handle(Future.succeededFuture(new MailCredentials(email, password))); + }); + } + + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MemberDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MemberDataHandler.java new file mode 100644 index 0000000..692dd98 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MemberDataHandler.java @@ -0,0 +1,65 @@ +package net.miarma.api.microservices.huertos.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.microservices.huertos.entities.MemberEntity; +import net.miarma.api.microservices.huertos.services.MemberService; + +@SuppressWarnings("unused") +public class MemberDataHandler { + private final MemberService memberService; + + public MemberDataHandler(Pool pool) { + this.memberService = new MemberService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + memberService.getAll(params) + .onSuccess(members -> JsonUtil.sendJson(ctx, ApiStatus.OK, members)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer id = Integer.parseInt(ctx.request().getParam("user_id")); + + memberService.getById(id) + .onSuccess(member -> { + if (member == null) { + JsonUtil.sendJson(ctx, ApiStatus.NOT_FOUND, null, "Member not found"); + } else { + JsonUtil.sendJson(ctx, ApiStatus.OK, member); + } + }) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + MemberEntity member = Constants.GSON.fromJson(ctx.body().asString(), MemberEntity.class); + + memberService.create(member) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + MemberEntity member = Constants.GSON.fromJson(ctx.body().asString(), MemberEntity.class); + + memberService.update(member) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer id = Integer.parseInt(ctx.request().getParam("user_id")); + + memberService.delete(id) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MemberLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MemberLogicHandler.java new file mode 100644 index 0000000..9db3478 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/MemberLogicHandler.java @@ -0,0 +1,202 @@ +package net.miarma.api.microservices.huertos.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; +import net.miarma.api.microservices.huertos.entities.PreUserEntity; + +public class MemberLogicHandler { + private final Vertx vertx; + + public MemberLogicHandler(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.HUERTOS_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(), "The member is inactive or banned"); + } + }); + } + + public void getByMemberNumber(RoutingContext ctx) { + Integer memberNumber = Integer.parseInt(ctx.request().getParam("member_number")); + JsonObject request = new JsonObject().put("action", "getByMemberNumber").put("memberNumber", memberNumber); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void getByPlotNumber(RoutingContext ctx) { + Integer plotNumber = Integer.parseInt(ctx.request().getParam("plot_number")); + JsonObject request = new JsonObject().put("action", "getByPlotNumber").put("plotNumber", plotNumber); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void getByDni(RoutingContext ctx) { + String dni = ctx.request().getParam("dni"); + JsonObject request = new JsonObject().put("action", "getByDNI").put("dni", dni); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void getUserPayments(RoutingContext ctx) { + Integer memberNumber = Integer.parseInt(ctx.request().getParam("member_number")); + JsonObject request = new JsonObject().put("action", "getUserPayments").put("memberNumber", memberNumber); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void hasPaid(RoutingContext ctx) { + Integer memberNumber = Integer.parseInt(ctx.request().getParam("member_number")); + JsonObject request = new JsonObject().put("action", "hasPaid").put("memberNumber", memberNumber); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void getWaitlist(RoutingContext ctx) { + JsonObject request = new JsonObject().put("action", "getWaitlist"); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Waitlist not found"); + }); + } + + public void getLimitedWaitlist(RoutingContext ctx) { + JsonObject request = new JsonObject().put("action", "getLimitedWaitlist"); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Waitlist not found"); + }); + } + + public void getLastMemberNumber(RoutingContext ctx) { + JsonObject request = new JsonObject().put("action", "getLastMemberNumber"); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Last member number not found"); + }); + } + + public void getProfile(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "getProfile").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Profile not found"); + }); + } + + public void hasCollaborator(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "hasCollaborator").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Profile not found"); + }); + } + + public void hasCollaboratorRequest(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "hasCollaboratorRequest").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Profile not found"); + }); + } + + public void hasGreenHouse(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "hasGreenHouse").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Profile not found"); + }); + } + + public void hasGreenHouseRequest(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "hasGreenHouseRequest").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Profile not found"); + }); + } + + public void changeMemberStatus(RoutingContext ctx) { + Integer memberNumber = Integer.parseInt(ctx.request().getParam("member_number")); + String status = ctx.request().getParam("status"); + JsonObject request = new JsonObject() + .put("action", "changeMemberStatus") + .put("memberNumber", memberNumber) + .put("status", status); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void changeMemberType(RoutingContext ctx) { + Integer memberNumber = Integer.parseInt(ctx.request().getParam("member_number")); + String type = ctx.request().getParam("type"); + JsonObject request = new JsonObject() + .put("action", "changeMemberType") + .put("memberNumber", memberNumber) + .put("type", type); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Member not found"); + }); + } + + public void validatePreUser(RoutingContext ctx) { + PreUserEntity preUser = Constants.GSON.fromJson(ctx.body().asJsonObject().encode(), PreUserEntity.class); + JsonObject request = new JsonObject() + .put("action", "validatePreUser") + .put("preUser", Constants.GSON.toJson(preUser)); + + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) { + JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + } else { + Throwable cause = ar.cause(); + + if (cause instanceof io.vertx.core.eventbus.ReplyException replyEx && replyEx.failureCode() == 400) { + JsonObject errors = new JsonObject(replyEx.getMessage()); + JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, errors.encode()); + } else { + EventBusUtil.handleReplyError(ctx, cause, "Error validating pre-user"); + } + } + }); + } + + +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/PreUserDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/PreUserDataHandler.java new file mode 100644 index 0000000..978d3e9 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/PreUserDataHandler.java @@ -0,0 +1,58 @@ +package net.miarma.api.microservices.huertos.handlers; + +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.http.QueryParams; +import net.miarma.api.microservices.huertos.entities.PreUserEntity; +import net.miarma.api.microservices.huertos.services.PreUserService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class PreUserDataHandler { + private final PreUserService preUserService; + + public PreUserDataHandler(Pool pool) { + this.preUserService = new PreUserService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + preUserService.getAll(params) + .onSuccess(preUsers -> JsonUtil.sendJson(ctx, ApiStatus.OK, preUsers)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer id = Integer.parseInt(ctx.request().getParam("id")); + + preUserService.getById(id) + .onSuccess(preUser -> JsonUtil.sendJson(ctx, ApiStatus.OK, preUser)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + PreUserEntity preUser = net.miarma.api.backlib.Constants.GSON.fromJson(ctx.body().asString(), PreUserEntity.class); + + preUserService.create(preUser) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + PreUserEntity preUser = net.miarma.api.backlib.Constants.GSON.fromJson(ctx.body().asString(), PreUserEntity.class); + + preUserService.update(preUser) + .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 id = Integer.parseInt(ctx.request().getParam("pre_user_id")); + + preUserService.delete(id) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/PreUserLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/PreUserLogicHandler.java new file mode 100644 index 0000000..d16bc83 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/PreUserLogicHandler.java @@ -0,0 +1,5 @@ +package net.miarma.api.microservices.huertos.handlers; + +public class PreUserLogicHandler { + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/RequestDataHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/RequestDataHandler.java new file mode 100644 index 0000000..8c8aae9 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/RequestDataHandler.java @@ -0,0 +1,58 @@ +package net.miarma.api.microservices.huertos.handlers; + +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.http.QueryParams; +import net.miarma.api.microservices.huertos.entities.RequestEntity; +import net.miarma.api.microservices.huertos.services.RequestService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class RequestDataHandler { + private final RequestService requestService; + + public RequestDataHandler(Pool pool) { + this.requestService = new RequestService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + requestService.getAll(params) + .onSuccess(requests -> JsonUtil.sendJson(ctx, ApiStatus.OK, requests)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer requestId = Integer.parseInt(ctx.pathParam("request_id")); + + requestService.getById(requestId) + .onSuccess(request -> JsonUtil.sendJson(ctx, ApiStatus.OK, request)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + RequestEntity request = net.miarma.api.backlib.Constants.GSON.fromJson(ctx.body().asString(), RequestEntity.class); + + requestService.create(request) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + RequestEntity request = net.miarma.api.backlib.Constants.GSON.fromJson(ctx.body().asString(), RequestEntity.class); + + requestService.update(request) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer requestId = Integer.parseInt(ctx.pathParam("request_id")); + + requestService.delete(requestId) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/RequestLogicHandler.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/RequestLogicHandler.java new file mode 100644 index 0000000..e71423f --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/handlers/RequestLogicHandler.java @@ -0,0 +1,70 @@ +package net.miarma.api.microservices.huertos.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class RequestLogicHandler { + private final Vertx vertx; + + public RequestLogicHandler(Vertx vertx) { + this.vertx = vertx; + } + + public void getRequestsWithPreUsers(RoutingContext ctx) { + JsonObject request = new JsonObject().put("action", "getRequestsWithPreUsers"); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "No requests found"); + }); + } + + public void getRequestWithPreUser(RoutingContext ctx) { + Integer requestId = Integer.parseInt(ctx.request().getParam("request_id")); + JsonObject request = new JsonObject().put("action", "getRequestWithPreUser").put("requestId", requestId); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Request not found"); + }); + } + + public void getRequestCount(RoutingContext ctx) { + JsonObject request = new JsonObject().put("action", "getRequestCount"); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "No requests found"); + }); + } + + public void getMyRequests(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "getMyRequests").put("token", token); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "No requests found"); + }); + } + + public void acceptRequest(RoutingContext ctx) { + Integer requestId = Integer.parseInt(ctx.request().getParam("request_id")); + JsonObject request = new JsonObject().put("action", "acceptRequest").put("requestId", requestId); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Request not found"); + }); + } + + public void rejectRequest(RoutingContext ctx) { + Integer requestId = Integer.parseInt(ctx.request().getParam("request_id")); + JsonObject request = new JsonObject().put("action", "rejectRequest").put("requestId", requestId); + vertx.eventBus().request(Constants.HUERTOS_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Request not found"); + }); + } + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/Attachment.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/Attachment.java new file mode 100644 index 0000000..e93557f --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/Attachment.java @@ -0,0 +1,72 @@ +package net.miarma.api.microservices.huertos.mail; + +import java.util.Arrays; +import java.util.Base64; + +public class Attachment { + private String filename; + private String mimeType; + private transient byte[] data; + + public Attachment() {} + + public Attachment(String filename, String mimeType, byte[] data) { + this.filename = filename; + this.mimeType = mimeType; + this.data = data; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public String getBase64Data() { + return Base64.getEncoder().encodeToString(data); + } + + @Override + public String toString() { + return "AttachmentEntity [filename=" + filename + ", mimeType=" + mimeType + ", data=" + Arrays.toString(data) + + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(data); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Attachment other = (Attachment) obj; + return Arrays.equals(data, other.data); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/ImapReader.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/ImapReader.java new file mode 100644 index 0000000..e25e3c4 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/ImapReader.java @@ -0,0 +1,153 @@ +package net.miarma.api.microservices.huertos.mail; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Properties; + +import jakarta.mail.BodyPart; +import jakarta.mail.Folder; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.Part; +import jakarta.mail.Session; +import jakarta.mail.Store; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeUtility; +import net.miarma.api.backlib.ConfigManager; + +public class ImapReader { + + public static List listInboxEmails(String folder, String username, String password) throws Exception { + Properties props = new Properties(); + props.put("mail.store.protocol", "imaps"); + System.setProperty("jakarta.mail.streamprovider", "jakarta.mail.util.DefaultStreamProvider"); + + Session session = Session.getInstance(props, null); + Store store = session.getStore(); + store.connect( + ConfigManager.getInstance().getStringProperty("imap.server"), + ConfigManager.getInstance().getIntProperty("imap.port"), + username, password + ); + + Folder f = store.getFolder(folder); + f.open(Folder.READ_ONLY); + + Message[] messages = f.getMessages(); + List emails = new java.util.ArrayList<>(); + + for (Message message : messages) { + String from = message.getFrom() != null ? message.getFrom()[0].toString() : "Desconocido"; + from = MimeUtility.decodeText(from); + List toList = new java.util.ArrayList<>(); + if (message.getAllRecipients() != null) { + for (var address : message.getAllRecipients()) { + toList.add(address.toString()); + } + } + + String subject = message.getSubject(); + String content = extractContent(message); + LocalDateTime date = message.getSentDate() != null + ? message.getSentDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime() + : LocalDateTime.now(); + + List attachments = extractAttachments(message); + + emails.add(new Mail(from, toList, subject, content, date, attachments)); + } + + f.close(false); + store.close(); + + return emails; + } + + public static Mail getEmailByIndex(String folder, String username, String password, int index) throws Exception { + Properties props = new Properties(); + props.put("mail.store.protocol", "imaps"); + System.setProperty("jakarta.mail.streamprovider", "jakarta.mail.util.DefaultStreamProvider"); + + Session session = Session.getInstance(props, null); + Store store = session.getStore(); + store.connect( + ConfigManager.getInstance().getStringProperty("imap.server"), + ConfigManager.getInstance().getIntProperty("imap.port"), + username, password + ); + + Folder f = store.getFolder(folder); + f.open(Folder.READ_ONLY); + + Message[] messages = f.getMessages(); + + if (index < 0 || index >= messages.length) { + throw new IndexOutOfBoundsException("No message found with such index"); + } + + Message message = messages[index]; + String from = message.getFrom() != null ? message.getFrom()[0].toString() : "Desconocido"; + from = MimeUtility.decodeText(from); + List toList = new java.util.ArrayList<>(); + if (message.getAllRecipients() != null) { + for (var address : message.getAllRecipients()) { + toList.add(address.toString()); + } + } + + String subject = message.getSubject(); + String content = extractContent(message); + LocalDateTime date = message.getSentDate() != null + ? message.getSentDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime() + : LocalDateTime.now(); + + List attachments = extractAttachments(message); + + f.close(false); + store.close(); + + return new Mail(from, toList, subject, content, date, attachments); + } + + private static String extractContent(Message message) throws Exception { + Object content = message.getContent(); + if (content instanceof String) { + return (String) content; + } else if (content instanceof Multipart) { + Multipart multipart = (Multipart) content; + for (int i = 0; i < multipart.getCount(); i++) { + BodyPart part = multipart.getBodyPart(i); + if (part.isMimeType("text/plain")) { + return part.getContent().toString(); + } else if (part.isMimeType("text/html")) { + return part.getContent().toString(); + } + } + } + return ""; + } + + private static List extractAttachments(Message message) throws Exception { + List attachments = new java.util.ArrayList<>(); + + if (message.getContent() instanceof Multipart multipart) { + for (int i = 0; i < multipart.getCount(); i++) { + BodyPart part = multipart.getBodyPart(i); + + if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) && part.getFileName() != null) { + String filename = part.getFileName(); + String mimeType = part.getContentType().split(";")[0]; + + try (var inputStream = part.getInputStream()) { + byte[] data = inputStream.readAllBytes(); + attachments.add(new Attachment(filename, mimeType, data)); + } + } + } + } + + return attachments; + } + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/Mail.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/Mail.java new file mode 100644 index 0000000..2088cff --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/Mail.java @@ -0,0 +1,85 @@ +package net.miarma.api.microservices.huertos.mail; + +import java.time.LocalDateTime; +import java.util.List; + +public class Mail implements Comparable { + private String from; + private List to; + private String subject; + private String content; + private LocalDateTime date; + private List attachments; + + public Mail() {} + + public Mail(String from, List to, String subject, String content, LocalDateTime date, + List attachments) { + this.from = from; + this.to = to; + this.subject = subject; + this.content = content; + this.date = date; + this.attachments = attachments; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public List getTo() { + return to; + } + + public void setTo(List to) { + this.to = to; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getDate() { + return date; + } + + public void setDate(LocalDateTime date) { + this.date = date; + } + + public List getAttachments() { + return attachments; + } + + public void setAttachments(List attachments) { + this.attachments = attachments; + } + + @Override + public String toString() { + return "MailEntity [from=" + from + ", to=" + to + ", subject=" + subject + ", content=" + content + ", date=" + + date + ", attachments=" + attachments + "]"; + } + + @Override + public int compareTo(Mail other) { + return this.date.compareTo(other.date); + } + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/MailCredentials.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/MailCredentials.java new file mode 100644 index 0000000..54cfd8c --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/MailCredentials.java @@ -0,0 +1,5 @@ +package net.miarma.api.microservices.huertos.mail; + +public record MailCredentials(String username, String password) { + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/MailManager.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/MailManager.java new file mode 100644 index 0000000..7945ae2 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/mail/MailManager.java @@ -0,0 +1,126 @@ +package net.miarma.api.microservices.huertos.mail; + +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.mail.LoginOption; +import io.vertx.ext.mail.MailAttachment; +import io.vertx.ext.mail.MailClient; +import io.vertx.ext.mail.MailConfig; +import io.vertx.ext.mail.MailMessage; +import io.vertx.ext.mail.MailResult; +import io.vertx.ext.mail.StartTLSOptions; +import jakarta.mail.Folder; +import jakarta.mail.Message; +import jakarta.mail.Session; +import jakarta.mail.Store; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import net.miarma.api.backlib.ConfigManager; + +public class MailManager { + private final Vertx vertx; + private final String smtpHost; + private final int smtpPort; + private final Map clientCache = new ConcurrentHashMap<>(); + + public MailManager(Vertx vertx) { + this.vertx = vertx; + this.smtpHost = ConfigManager.getInstance().getStringProperty("smtp.server"); + this.smtpPort = ConfigManager.getInstance().getIntProperty("smtp.port"); + } + + private MailClient getClientForUser(String username, String password) { + return clientCache.computeIfAbsent(username, _ -> { + MailConfig config = new MailConfig() + .setHostname(smtpHost) + .setPort(smtpPort) + .setStarttls(StartTLSOptions.REQUIRED) + .setLogin(LoginOption.REQUIRED) + .setUsername(username) + .setPassword(password) + .setKeepAlive(true); + return MailClient.createShared(vertx, config, "mail-pool-" + username); + }); + } + + public static void storeInSentFolder(Mail mail, String username, String password) { + try { + Properties props = new Properties(); + props.put("mail.store.protocol", "imaps"); + props.put("mail.imaps.host", ConfigManager.getInstance().getStringProperty("imap.server")); + props.put("mail.imaps.port", ConfigManager.getInstance().getIntProperty("imap.port")); + + Session session = Session.getInstance(props); + Store store = session.getStore("imaps"); + store.connect(username, password); + + Folder sentFolder = store.getFolder("Sent"); + if (!sentFolder.exists()) { + sentFolder.create(Folder.HOLDS_MESSAGES); + } + sentFolder.open(Folder.READ_WRITE); + + MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress(mail.getFrom())); + for (String to : mail.getTo()) { + message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); + } + message.setSubject(mail.getSubject()); + message.setText(mail.getContent()); + + sentFolder.appendMessages(new Message[]{message}); + sentFolder.close(false); + store.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void sendEmail(Mail mail, String username, String password, Handler> resultHandler) { + MailMessage message = new MailMessage() + .setFrom(mail.getFrom()) + .setTo(mail.getTo()) + .setSubject(mail.getSubject()) + .setText(mail.getContent()); + getClientForUser(username, password).sendMail(message, ar -> { + resultHandler.handle(ar); + if(ar.succeeded()) { + storeInSentFolder(mail, username, password); + } + }); + } + + public void sendEmailWithAttachment(Mail mail, List attachments, String username, String password, + Handler> resultHandler) { + List mailAttachments = attachments.stream().map(a -> { + return MailAttachment.create() + .setData(Buffer.buffer(a.getData())) + .setName(a.getFilename()) + .setContentType(a.getMimeType()) + .setDisposition("attachment"); + }).collect(Collectors.toList()); + + MailMessage message = new MailMessage() + .setFrom(mail.getFrom()) + .setTo(mail.getTo()) + .setSubject(mail.getSubject()) + .setText(mail.getContent()) + .setAttachment(mailAttachments); + + getClientForUser(username, password).sendMail(message, ar -> { + resultHandler.handle(ar); + if(ar.succeeded()) { + storeInSentFolder(mail, username, password); + } + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosDataRouter.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosDataRouter.java new file mode 100644 index 0000000..0563718 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosDataRouter.java @@ -0,0 +1,76 @@ +package net.miarma.api.microservices.huertos.routing; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants.HuertosUserRole; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.huertos.handlers.AnnouncementDataHandler; +import net.miarma.api.microservices.huertos.handlers.BalanceDataHandler; +import net.miarma.api.microservices.huertos.handlers.ExpenseDataHandler; +import net.miarma.api.microservices.huertos.handlers.IncomeDataHandler; +import net.miarma.api.microservices.huertos.handlers.MemberDataHandler; +import net.miarma.api.microservices.huertos.handlers.PreUserDataHandler; +import net.miarma.api.microservices.huertos.handlers.RequestDataHandler; +import net.miarma.api.microservices.huertos.routing.middlewares.HuertosAuthGuard; +import net.miarma.api.microservices.huertos.services.MemberService; + +public class HuertosDataRouter { + + public static void mount(Router router, Vertx vertx, Pool pool) { + AnnouncementDataHandler hAnnounceData = new AnnouncementDataHandler(pool); + BalanceDataHandler hBalanceData = new BalanceDataHandler(pool); + ExpenseDataHandler hExpenseData = new ExpenseDataHandler(pool); + IncomeDataHandler hIncomeData = new IncomeDataHandler(pool); + MemberDataHandler hMemberData = new MemberDataHandler(pool); + PreUserDataHandler hPreUserData = new PreUserDataHandler(pool); + RequestDataHandler hRequestData = new RequestDataHandler(pool); + MemberService memberService = new MemberService(pool); + HuertosAuthGuard authGuard = new HuertosAuthGuard(memberService); + + router.route().handler(BodyHandler.create()); + + router.get(HuertosEndpoints.ANNOUNCES).handler(authGuard.check()).handler(hAnnounceData::getAll); + router.get(HuertosEndpoints.ANNOUNCE).handler(authGuard.check()).handler(hAnnounceData::getById); + router.post(HuertosEndpoints.ANNOUNCES).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hAnnounceData::create); + router.put(HuertosEndpoints.ANNOUNCE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hAnnounceData::update); + router.delete(HuertosEndpoints.ANNOUNCE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hAnnounceData::delete); + + router.get(HuertosEndpoints.BALANCE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hBalanceData::getBalance); + router.post(HuertosEndpoints.BALANCE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hBalanceData::update); + router.delete(HuertosEndpoints.BALANCE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hBalanceData::create); + + router.get(HuertosEndpoints.EXPENSES).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hExpenseData::getAll); + router.get(HuertosEndpoints.EXPENSE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hExpenseData::getById); + router.post(HuertosEndpoints.EXPENSES).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hExpenseData::create); + router.put(HuertosEndpoints.EXPENSE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hExpenseData::update); + router.delete(HuertosEndpoints.EXPENSE).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hExpenseData::delete); + + router.get(HuertosEndpoints.INCOMES).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hIncomeData::getAll); + router.get(HuertosEndpoints.INCOME).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hIncomeData::getById); + router.post(HuertosEndpoints.INCOMES).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hIncomeData::create); + router.put(HuertosEndpoints.INCOME).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hIncomeData::update); + router.delete(HuertosEndpoints.INCOME).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hIncomeData::delete); + router.get(HuertosEndpoints.INCOMES_WITH_NAMES).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hIncomeData::getIncomesWithNames); + + router.get(HuertosEndpoints.MEMBERS).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hMemberData::getAll); + router.get(HuertosEndpoints.MEMBER).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hMemberData::getById); + router.post(HuertosEndpoints.MEMBERS).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hMemberData::create); + router.put(HuertosEndpoints.MEMBER).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hMemberData::update); + router.delete(HuertosEndpoints.MEMBER).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hMemberData::delete); + + router.get(HuertosEndpoints.PRE_USERS).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hPreUserData::getAll); + router.get(HuertosEndpoints.PRE_USER).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hPreUserData::getById); + router.post(HuertosEndpoints.PRE_USERS).handler(hPreUserData::create); + router.put(HuertosEndpoints.PRE_USER).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hPreUserData::update); + router.delete(HuertosEndpoints.PRE_USER).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hPreUserData::delete); + + router.get(HuertosEndpoints.REQUESTS).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hRequestData::getAll); + router.get(HuertosEndpoints.REQUEST).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hRequestData::getById); + router.post(HuertosEndpoints.REQUESTS).handler(hRequestData::create); + router.put(HuertosEndpoints.REQUEST).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hRequestData::update); + router.delete(HuertosEndpoints.REQUEST).handler(authGuard.check(HuertosUserRole.ADMIN)).handler(hRequestData::delete); + + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosEndpoints.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosEndpoints.java new file mode 100644 index 0000000..3e4e4a7 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosEndpoints.java @@ -0,0 +1,67 @@ +package net.miarma.api.microservices.huertos.routing; + +import net.miarma.api.backlib.Constants; + +public class HuertosEndpoints { + // auth + public static final String LOGIN = Constants.HUERTOS_PREFIX + "/login"; + + // socios -> GET, POST, PUT, DELETE + public static final String MEMBERS = Constants.HUERTOS_PREFIX + "/members"; // GET, POST, PUT, DELETE + public static final String MEMBER = Constants.HUERTOS_PREFIX + "/members/:user_id"; // GET, POST, PUT, DELETE por id + public static final String MEMBER_BY_NUMBER = Constants.HUERTOS_PREFIX + "/members/number/:member_number"; // GET por número de socio + public static final String MEMBER_BY_PLOT = Constants.HUERTOS_PREFIX + "/members/plot/:plot_number"; // GET por número de parcela + public static final String MEMBER_BY_DNI = Constants.HUERTOS_PREFIX + "/members/dni/:dni"; // GET por DNI + public static final String MEMBER_PAYMENTS = Constants.HUERTOS_PREFIX + "/members/number/:member_number/incomes"; // GET ingresos de ese miembro + public static final String MEMBER_HAS_PAID = Constants.HUERTOS_PREFIX + "/members/number/:member_number/has-paid"; // GET si ha pagado + public static final String MEMBER_WAITLIST = Constants.HUERTOS_PREFIX + "/members/waitlist"; // GET todos los de la lista de espera + public static final String MEMBER_LIMITED_WAITLIST = Constants.HUERTOS_PREFIX + "/members/waitlist/limited"; // GET lista limitada + public static final String LAST_MEMBER_NUMBER = Constants.HUERTOS_PREFIX + "/members/latest-number"; // GET último número de socio usado + public static final String MEMBER_PROFILE = Constants.HUERTOS_PREFIX + "/members/profile"; // GET perfil del usuario logado (socio o admin) + public static final String MEMBER_HAS_COLLABORATOR = Constants.HUERTOS_PREFIX + "/members/number/:member_number/has-collaborator"; // GET si tiene colaborador asignado + public static final String MEMBER_HAS_COLLABORATOR_REQUEST = Constants.HUERTOS_PREFIX + "/members/number/:member_number/has-collaborator-request"; // GET si tiene solicitud de colaborador asignada + public static final String MEMBER_HAS_GREENHOUSE = Constants.HUERTOS_PREFIX + "/members/number/:member_number/has-greenhouse"; // GET si tiene invernadero asignado + public static final String MEMBER_HAS_GREENHOUSE_REQUEST = Constants.HUERTOS_PREFIX + "/members/number/:member_number/has-greenhouse-request"; // GET si tiene solicitud de invernadero asignada + public static final String CHANGE_MEMBER_STATUS = Constants.HUERTOS_PREFIX + "/members/:user_id/status"; // PUT cambiar estado de socio (activo, inactivo, baja, etc.) + public static final String CHANGE_MEMBER_TYPE = Constants.HUERTOS_PREFIX + "/members/:user_id/type"; // PUT cambiar tipo de socio (socio, colaborador, etc.) + + // ingresos -> GET, POST, PUT, DELETE + public static final String INCOMES = Constants.HUERTOS_PREFIX + "/incomes"; + public static final String INCOMES_WITH_NAMES = Constants.HUERTOS_PREFIX + "/incomes-with-names"; + public static final String INCOME = Constants.HUERTOS_PREFIX + "/incomes/:income_id"; + public static final String MY_INCOMES = Constants.HUERTOS_PREFIX + "/incomes/my-incomes"; // GET ingresos del usuario logado (socio o admin) + + // gastos -> GET, POST, PUT, DELETE + public static final String EXPENSES = Constants.HUERTOS_PREFIX + "/expenses"; + public static final String EXPENSE = Constants.HUERTOS_PREFIX + "/expenses/:expense_id"; + + // balance -> GET, POST, PUT, DELETE + public static final String BALANCE = Constants.HUERTOS_PREFIX + "/balance"; + public static final String BALANCE_WITH_TOTALS = Constants.HUERTOS_PREFIX + "/balance/with-totals"; + + // anuncios -> GET, POST, PUT, DELETE + public static final String ANNOUNCES = Constants.HUERTOS_PREFIX + "/announces"; + public static final String ANNOUNCE = Constants.HUERTOS_PREFIX + "/announces/:announce_id"; + + // solicitudes -> GET, POST, PUT, DELETE + public static final String REQUESTS = Constants.HUERTOS_PREFIX + "/requests"; + public static final String REQUEST = Constants.HUERTOS_PREFIX + "/requests/:request_id"; + public static final String REQUEST_COUNT = Constants.HUERTOS_PREFIX + "/requests/count"; // GET número de solicitudes + public static final String MY_REQUESTS = Constants.HUERTOS_PREFIX + "/requests/my-requests"; // GET solicitudes del usuario logado (socio o admin) + public static final String ACCEPT_REQUEST = Constants.HUERTOS_PREFIX + "/requests/:request_id/accept"; // PUT aceptar solicitud + public static final String REJECT_REQUEST = Constants.HUERTOS_PREFIX + "/requests/:request_id/reject"; // PUT rechazar solicitud + + // pre-socios -> GET, POST, PUT, DELETE + public static final String PRE_USERS = Constants.HUERTOS_PREFIX + "/pre_users"; + public static final String PRE_USER = Constants.HUERTOS_PREFIX + "/pre_users/:pre_user_id"; + public static final String PRE_USER_VALIDATE = Constants.HUERTOS_PREFIX + "/pre_users/validate"; // POST validar pre-socio + + // solicitud + pre-socio -> GET + public static final String REQUESTS_WITH_PRE_USERS = Constants.HUERTOS_PREFIX + "/requests-full"; + public static final String REQUEST_WITH_PRE_USER = Constants.HUERTOS_PREFIX + "/requests-full/:request_id"; + + // mail + public static final String SEND_MAIL = Constants.HUERTOS_PREFIX + "/mails/send"; // POST + public static final String MAIL = Constants.HUERTOS_PREFIX + "/mails/:folder/:index"; // GET + public static final String MAILS = Constants.HUERTOS_PREFIX + "/mails/:folder"; // GET +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosLogicRouter.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosLogicRouter.java new file mode 100644 index 0000000..a138d50 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/HuertosLogicRouter.java @@ -0,0 +1,60 @@ + package net.miarma.api.microservices.huertos.routing; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.huertos.handlers.BalanceLogicHandler; +import net.miarma.api.microservices.huertos.handlers.IncomeLogicHandler; +import net.miarma.api.microservices.huertos.handlers.MailHandler; +import net.miarma.api.microservices.huertos.handlers.MemberLogicHandler; +import net.miarma.api.microservices.huertos.handlers.RequestLogicHandler; +import net.miarma.api.microservices.huertos.routing.middlewares.HuertosAuthGuard; +import net.miarma.api.microservices.huertos.services.MemberService; + +public class HuertosLogicRouter { + public static void mount(Router router, Vertx vertx, Pool pool) { + MemberLogicHandler hMemberLogic = new MemberLogicHandler(vertx); + MemberService memberService = new MemberService(pool); + IncomeLogicHandler hIncomeLogic = new IncomeLogicHandler(vertx); + BalanceLogicHandler hBalanceLogic = new BalanceLogicHandler(vertx); + RequestLogicHandler hRequestLogic = new RequestLogicHandler(vertx); + MailHandler hMail = new MailHandler(vertx, pool); + HuertosAuthGuard authGuard = new HuertosAuthGuard(memberService); + + router.route().handler(BodyHandler.create()); + + router.post(HuertosEndpoints.LOGIN).handler(hMemberLogic::login); + router.get(HuertosEndpoints.MEMBER_BY_NUMBER).handler(authGuard.check()).handler(hMemberLogic::getByMemberNumber); + router.get(HuertosEndpoints.MEMBER_BY_PLOT).handler(authGuard.check()).handler(hMemberLogic::getByPlotNumber); + router.get(HuertosEndpoints.MEMBER_BY_DNI).handler(authGuard.check()).handler(hMemberLogic::getByDni); + router.get(HuertosEndpoints.MEMBER_PAYMENTS).handler(authGuard.check()).handler(hMemberLogic::getUserPayments); + router.get(HuertosEndpoints.MEMBER_HAS_PAID).handler(authGuard.check()).handler(hMemberLogic::hasPaid); + router.get(HuertosEndpoints.MEMBER_WAITLIST).handler(authGuard.check()).handler(hMemberLogic::getWaitlist); + router.get(HuertosEndpoints.MEMBER_LIMITED_WAITLIST).handler(hMemberLogic::getLimitedWaitlist); + router.get(HuertosEndpoints.LAST_MEMBER_NUMBER).handler(hMemberLogic::getLastMemberNumber); + router.get(HuertosEndpoints.BALANCE_WITH_TOTALS).handler(authGuard.check()).handler(hBalanceLogic::getBalanceWithTotals); + router.get(HuertosEndpoints.REQUESTS_WITH_PRE_USERS).handler(authGuard.check()).handler(hRequestLogic::getRequestsWithPreUsers); + router.get(HuertosEndpoints.REQUEST_WITH_PRE_USER).handler(authGuard.check()).handler(hRequestLogic::getRequestWithPreUser); + router.get(HuertosEndpoints.MEMBER_PROFILE).handler(hMemberLogic::getProfile); + router.get(HuertosEndpoints.REQUEST_COUNT).handler(authGuard.check()).handler(hRequestLogic::getRequestCount); + router.get(HuertosEndpoints.MY_INCOMES).handler(authGuard.check()).handler(hIncomeLogic::getMyIncomes); + router.get(HuertosEndpoints.MY_REQUESTS).handler(authGuard.check()).handler(hRequestLogic::getMyRequests); + router.put(HuertosEndpoints.ACCEPT_REQUEST).handler(authGuard.check()).handler(hRequestLogic::acceptRequest); + router.put(HuertosEndpoints.REJECT_REQUEST).handler(authGuard.check()).handler(hRequestLogic::rejectRequest); + router.put(HuertosEndpoints.CHANGE_MEMBER_STATUS).handler(authGuard.check()).handler(hMemberLogic::changeMemberStatus); + router.put(HuertosEndpoints.CHANGE_MEMBER_TYPE).handler(authGuard.check()).handler(hMemberLogic::changeMemberType); + router.get(HuertosEndpoints.MEMBER_HAS_COLLABORATOR).handler(authGuard.check()).handler(hMemberLogic::hasCollaborator); + router.get(HuertosEndpoints.MEMBER_HAS_COLLABORATOR_REQUEST).handler(authGuard.check()).handler(hMemberLogic::hasCollaboratorRequest); + router.get(HuertosEndpoints.MEMBER_HAS_GREENHOUSE).handler(authGuard.check()).handler(hMemberLogic::hasGreenHouse); + router.get(HuertosEndpoints.MEMBER_HAS_GREENHOUSE_REQUEST).handler(authGuard.check()).handler(hMemberLogic::hasGreenHouseRequest); + router.post(HuertosEndpoints.PRE_USER_VALIDATE).handler(hMemberLogic::validatePreUser); + + router.get(HuertosEndpoints.MAILS).handler(hMail::getFolder); + router.get(HuertosEndpoints.MAIL).handler(hMail::getMail); + router.post(HuertosEndpoints.SEND_MAIL).handler(hMail::sendMail); + + + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/middlewares/HuertosAuthGuard.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/middlewares/HuertosAuthGuard.java new file mode 100644 index 0000000..ae244fd --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/routing/middlewares/HuertosAuthGuard.java @@ -0,0 +1,35 @@ +package net.miarma.api.microservices.huertos.routing.middlewares; + +import java.util.function.Consumer; + +import io.vertx.ext.web.RoutingContext; +import net.miarma.api.backlib.Constants.HuertosUserRole; +import net.miarma.api.backlib.middlewares.AbstractAuthGuard; +import net.miarma.api.microservices.huertos.entities.MemberEntity; +import net.miarma.api.microservices.huertos.services.MemberService; + +public class HuertosAuthGuard extends AbstractAuthGuard { + private final MemberService memberService; + + public HuertosAuthGuard(MemberService memberService) { + this.memberService = memberService; + } + + @Override + protected HuertosUserRole parseRole(String roleStr) { + return HuertosUserRole.valueOf(roleStr.toUpperCase()); + } + + @Override + protected void getUserEntity(int userId, RoutingContext ctx, Consumer callback) { + memberService.getById(userId).onComplete(ar -> { + if (ar.succeeded()) callback.accept(ar.result()); + else callback.accept(null); + }); + } + + @Override + protected boolean hasPermission(MemberEntity user, HuertosUserRole role) { + return user.getRole() == HuertosUserRole.ADMIN; + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/AnnouncementService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/AnnouncementService.java new file mode 100644 index 0000000..db7d270 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/AnnouncementService.java @@ -0,0 +1,69 @@ +package net.miarma.api.microservices.huertos.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +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.microservices.huertos.dao.AnnouncementDAO; +import net.miarma.api.microservices.huertos.entities.AnnouncementEntity; +import net.miarma.api.microservices.huertos.validators.AnnouncementValidator; + +import java.util.List; + +public class AnnouncementService { + + private final AnnouncementDAO announcementDAO; + private final AnnouncementValidator announcementValidator; + + public AnnouncementService(Pool pool) { + this.announcementDAO = new AnnouncementDAO(pool); + this.announcementValidator = new AnnouncementValidator(); + } + + public Future> getAll(QueryParams params) { + return announcementDAO.getAll(params); + } + + public Future getById(Integer id) { + return announcementDAO.getById(id).compose(announce -> { + if (announce == null) { + return Future.failedFuture(new NotFoundException("Announce not found in the database")); + } + return Future.succeededFuture(announce); + }); + } + + public Future create(AnnouncementEntity announce) { + return announcementValidator.validate(announce).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return announcementDAO.insert(announce); + }); + } + + public Future update(AnnouncementEntity announce) { + return getById(announce.getAnnounce_id()).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Announce not found in the database")); + } + return announcementValidator.validate(announce).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return announcementDAO.update(announce); + }); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Announce not found in the database")); + } + return announcementDAO.delete(id); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/BalanceService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/BalanceService.java new file mode 100644 index 0000000..9a6529b --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/BalanceService.java @@ -0,0 +1,63 @@ +package net.miarma.api.microservices.huertos.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.exceptions.NotFoundException; +import net.miarma.api.backlib.exceptions.ValidationException; +import net.miarma.api.microservices.huertos.dao.BalanceDAO; +import net.miarma.api.microservices.huertos.entities.BalanceEntity; +import net.miarma.api.microservices.huertos.entities.ViewBalanceWithTotals; +import net.miarma.api.microservices.huertos.validators.BalanceValidator; + +public class BalanceService { + private final BalanceDAO balanceDAO; + private final BalanceValidator balanceValidator; + + public BalanceService(Pool pool) { + this.balanceDAO = new BalanceDAO(pool); + this.balanceValidator = new BalanceValidator(); + } + + public Future getBalance() { + return balanceDAO.getAll().compose(balanceList -> { + if (balanceList.isEmpty()) { + return Future.failedFuture(new NotFoundException("Balance in the database")); + } + return Future.succeededFuture(balanceList.getFirst()); + }); + } + + public Future getBalanceWithTotals() { + return balanceDAO.getAllWithTotals().compose(balanceList -> { + if (balanceList.isEmpty()) { + return Future.failedFuture(new NotFoundException("Balance in the database")); + } + return Future.succeededFuture(balanceList.getFirst()); + }); + } + + public Future update(BalanceEntity balance) { + return getBalance().compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Balance in the database")); + } + + return balanceValidator.validate(balance).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return balanceDAO.update(balance); + }); + }); + } + + public Future create(BalanceEntity balance) { + return balanceValidator.validate(balance).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return balanceDAO.insert(balance); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/ExpenseService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/ExpenseService.java new file mode 100644 index 0000000..a5e87cb --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/ExpenseService.java @@ -0,0 +1,74 @@ +package net.miarma.api.microservices.huertos.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +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.microservices.huertos.dao.ExpenseDAO; +import net.miarma.api.microservices.huertos.entities.ExpenseEntity; +import net.miarma.api.microservices.huertos.validators.ExpenseValidator; + +import java.util.List; + +public class ExpenseService { + + private final ExpenseDAO expenseDAO; + private final ExpenseValidator expenseValidator; + + public ExpenseService(Pool pool) { + this.expenseDAO = new ExpenseDAO(pool); + this.expenseValidator = new ExpenseValidator(); + } + + public Future> getAll(QueryParams params) { + return expenseDAO.getAll(params); + } + + public Future getById(Integer id) { + return expenseDAO.getAll().compose(expenses -> { + ExpenseEntity expense = expenses.stream() + .filter(e -> e.getExpense_id().equals(id)) + .findFirst() + .orElse(null); + if (expense == null) { + return Future.failedFuture(new NotFoundException("Expense with id " + id + " not found")); + } + return Future.succeededFuture(expense); + }); + } + + public Future create(ExpenseEntity expense) { + return expenseValidator.validate(expense).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return expenseDAO.insert(expense); + }); + } + + public Future update(ExpenseEntity expense) { + return getById(expense.getExpense_id()).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Expense not found")); + } + + return expenseValidator.validate(expense).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return expenseDAO.update(expense); + }); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(expense -> { + if (expense == null) { + return Future.failedFuture(new NotFoundException("Expense with id " + id + " not found")); + } + return expenseDAO.delete(id); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/IncomeService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/IncomeService.java new file mode 100644 index 0000000..5387057 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/IncomeService.java @@ -0,0 +1,92 @@ +package net.miarma.api.microservices.huertos.services; + +import java.util.List; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +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.security.JWTManager; +import net.miarma.api.microservices.huertos.dao.IncomeDAO; +import net.miarma.api.microservices.huertos.entities.IncomeEntity; +import net.miarma.api.microservices.huertos.entities.ViewIncomesWithFullNames; +import net.miarma.api.microservices.huertos.validators.IncomeValidator; + +public class IncomeService { + + private final IncomeDAO incomeDAO; + private final MemberService memberService; + private final IncomeValidator incomeValidator; + + public IncomeService(Pool pool) { + this.incomeDAO = new IncomeDAO(pool); + this.memberService = new MemberService(pool); + this.incomeValidator = new IncomeValidator(); + } + + public Future> getAll(QueryParams params) { + return incomeDAO.getAll(params); + } + + public Future> getIncomesWithNames(QueryParams params) { + return incomeDAO.getAllWithNames(params); + } + + public Future getById(Integer id) { + return incomeDAO.getById(id); + } + + public Future> getMyIncomes(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + return memberService.getById(userId).compose(memberEntity -> incomeDAO.getUserIncomes(memberEntity.getMember_number())); + + } + + public Future> getUserPayments(Integer memberNumber) { + return incomeDAO.getUserIncomes(memberNumber); + } + + public Future hasPaid(Integer memberNumber) { + return getUserPayments(memberNumber).compose(incomes -> { + boolean hasPaid = incomes.stream().anyMatch(IncomeEntity::isPaid); + return Future.succeededFuture(hasPaid); + }); + } + + public Future create(IncomeEntity income) { + return incomeValidator.validate(income).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return incomeDAO.insert(income); + }); + } + + public Future update(IncomeEntity income) { + return getById(income.getIncome_id()).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Income in the database")); + } + + return incomeValidator.validate(income).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return incomeDAO.update(income); + }); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(income -> { + if (income == null) { + return Future.failedFuture(new NotFoundException("Income with id " + id + " not found")); + } + return incomeDAO.delete(id); + }); + } + + +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/MemberService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/MemberService.java new file mode 100644 index 0000000..31795de --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/MemberService.java @@ -0,0 +1,304 @@ +package net.miarma.api.microservices.huertos.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.HuertosUserRole; +import net.miarma.api.backlib.Constants.HuertosUserStatus; +import net.miarma.api.backlib.Constants.HuertosUserType; +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.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.services.UserService; +import net.miarma.api.microservices.huertos.dao.MemberDAO; +import net.miarma.api.microservices.huertos.dao.UserMetadataDAO; +import net.miarma.api.microservices.huertos.entities.MemberEntity; +import net.miarma.api.microservices.huertos.entities.PreUserEntity; +import net.miarma.api.microservices.huertos.entities.UserMetadataEntity; +import net.miarma.api.microservices.huertos.validators.MemberValidator; + +@SuppressWarnings("unused") +public class MemberService { + + private final UserDAO userDAO; + private final UserMetadataDAO userMetadataDAO; + private final MemberDAO memberDAO; + private final UserService userService; + private final MemberValidator memberValidator; + + public MemberService(Pool pool) { + this.userDAO = new UserDAO(pool); + this.memberDAO = new MemberDAO(pool); + this.userMetadataDAO = new UserMetadataDAO(pool); + this.userService = new UserService(pool); + this.memberValidator = new MemberValidator(); + } + + public Future login(String emailOrUserName, String password, boolean keepLoggedIn) { + return userService.login(emailOrUserName, password, keepLoggedIn).compose(json -> { + JsonObject loggedUserJson = json.getJsonObject("loggedUser"); + UserEntity user = Constants.GSON.fromJson(loggedUserJson.encode(), UserEntity.class); + + 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")); + } + + + return userMetadataDAO.getById(user.getUser_id()).compose(metadata -> { + if (metadata.getStatus() != HuertosUserStatus.ACTIVE) { + return Future.failedFuture(new ForbiddenException("User is not active")); + } + + MemberEntity member = new MemberEntity(user, metadata); + + return Future.succeededFuture(new JsonObject() + .put("token", json.getString("token")) + .put("member", new JsonObject(Constants.GSON.toJson(member))) + ); + }); + }); + } + + public Future> getAll() { + return memberDAO.getAll().compose(list -> Future.succeededFuture(list.stream() + .filter(m -> !m.getType().equals(HuertosUserType.DEVELOPER)) + .toList())); + } + + public Future> getAll(QueryParams params) { + return memberDAO.getAll(params).compose(list -> Future.succeededFuture(list.stream() + .filter(m -> !m.getType().equals(HuertosUserType.DEVELOPER)) + .toList())); + } + + public Future getById(Integer id) { + return memberDAO.getById(id).compose(member -> { + if (member == null) { + return Future.failedFuture(new NotFoundException("Member with id " + id)); + } + return Future.succeededFuture(member); + }); + } + + public Future getByMemberNumber(Integer memberNumber) { + return memberDAO.getByMemberNumber(memberNumber).compose(member -> { + if (member == null) { + return Future.failedFuture(new NotFoundException("Member with member number " + memberNumber)); + } + return Future.succeededFuture(member); + }); + } + + public Future getByPlotNumber(Integer plotNumber) { + return memberDAO.getByPlotNumber(plotNumber).compose(member -> { + if (member == null) { + return Future.failedFuture(new NotFoundException("Member with plot number " + plotNumber)); + } + return Future.succeededFuture(member); + }); + } + + public Future getByEmail(String email) { + return memberDAO.getByEmail(email).compose(member -> { + if (member == null) { + return Future.failedFuture(new NotFoundException("Member with email " + email)); + } + return Future.succeededFuture(member); + }); + } + + public Future getByDni(String dni) { + return memberDAO.getByDni(dni).compose(member -> { + if (member == null) { + return Future.failedFuture(new NotFoundException("Member with DNI " + dni)); + } + return Future.succeededFuture(member); + }); + } + + public Future getByPhone(Integer phone) { + return memberDAO.getByPhone(phone).compose(member -> { + if (member == null) { + return Future.failedFuture(new NotFoundException("Member with phone " + phone)); + } + return Future.succeededFuture(member); + }); + } + + public Future> getWaitlist() { + return memberDAO.getWaitlist().compose(list -> { + if (list.isEmpty()) { + return Future.failedFuture(new NotFoundException("No members in the waitlist")); + } + return Future.succeededFuture(list); + }); + } + + public Future getLastMemberNumber() { + return memberDAO.getLastMemberNumber().compose(number -> { + if (number == null) { + return Future.failedFuture(new NotFoundException("No members found")); + } + return Future.succeededFuture(number); + }); + } + + public Future hasCollaborator(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + + return getById(userId).compose(member -> { + Integer plotNumber = member.getPlot_number(); + + if (plotNumber == null || plotNumber == 0) { + return Future.succeededFuture(false); + } + + return memberDAO.hasCollaborator(plotNumber).compose(hasCollaborator -> { + if (!hasCollaborator) { + return Future.failedFuture(new NotFoundException("User does not have a collaborator")); + } + return Future.succeededFuture(true); + }); + }); + } + + public Future getCollaborator(Integer plotNumber) { + return memberDAO.getCollaborator(plotNumber).compose(collaborator -> { + if (collaborator == null) { + return Future.failedFuture(new NotFoundException("No collaborator found for plot number " + plotNumber)); + } + return Future.succeededFuture(collaborator); + }); + } + + public Future hasGreenHouse(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + + return getById(userId).map(user -> user.getType() == HuertosUserType.WITH_GREENHOUSE); + } + + public Future updateRole(Integer userId, HuertosUserRole role) { + return getById(userId).compose(member -> { + member.setRole(role); + return userMetadataDAO.update(UserMetadataEntity.fromMemberEntity(member)) + .compose(updated -> getById(userId)); + }); + } + + public Future updateStatus(Integer userId, HuertosUserStatus status) { + return getById(userId).compose(member -> { + member.setStatus(status); + return userMetadataDAO.update(UserMetadataEntity.fromMemberEntity(member)) + .compose(updated -> getById(userId)); + }); + } + + public Future create(MemberEntity member) { + return memberValidator.validate(member).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + + member.setPassword(PasswordHasher.hash(member.getPassword())); + if (member.getEmail().isBlank()) member.setEmail(null); + + return userDAO.insert(UserEntity.from(member)).compose(user -> { + UserMetadataEntity metadata = UserMetadataEntity.fromMemberEntity(member); + metadata.setUser_id(user.getUser_id()); + + return userMetadataDAO.insert(metadata).compose(meta -> { + String baseName = member.getDisplay_name().split(" ")[0].toLowerCase(); + String userName = baseName + member.getMember_number(); + + user.setUser_name(userName); + + return userDAO.update(user).map(updatedUser -> new MemberEntity(updatedUser, meta)); + }); + }); + }); + } + + + public Future createFromPreUser(PreUserEntity preUser) { + MemberEntity memberFromPreUser = MemberEntity.fromPreUser(preUser); + return memberValidator.validate(memberFromPreUser).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + + memberFromPreUser.setPassword(PasswordHasher.hash(memberFromPreUser.getPassword())); + + return userDAO.insert(UserEntity.from(memberFromPreUser)).compose(user -> { + UserMetadataEntity metadata = UserMetadataEntity.fromMemberEntity(memberFromPreUser); + metadata.setUser_id(user.getUser_id()); + + return userMetadataDAO.insert(metadata) + .map(meta -> new MemberEntity(user, meta)); + }); + }); + } + + public Future update(MemberEntity member) { + return getById(member.getUser_id()).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Member in the database")); + } + + return memberValidator.validate(member).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + + if (member.getPassword() != null && !member.getPassword().isEmpty() && + !member.getPassword().equals(existing.getPassword())) { + member.setPassword(PasswordHasher.hash(member.getPassword())); + } else { + member.setPassword(existing.getPassword()); + } + + return userDAO.update(UserEntity.from(member)).compose(user -> userMetadataDAO.updateWithNulls(UserMetadataEntity.fromMemberEntity(member)) + .map(meta -> new MemberEntity(user, meta))); + }); + }); + } + + + public Future delete(Integer userId) { + return getById(userId).compose(member -> + userDAO.delete(userId).compose(deletedUser -> + userMetadataDAO.delete(member.getUser_id()) + .map(deletedMetadata -> member) + ) + ); + } + + public Future changeMemberStatus(Integer userId, HuertosUserStatus status) { + return getById(userId).compose(member -> { + member.setStatus(status); + return userMetadataDAO.update(UserMetadataEntity.fromMemberEntity(member)) + .map(updated -> member); + }); + } + + public Future changeMemberType(Integer userId, HuertosUserType type) { + return getById(userId).compose(member -> { + member.setType(type); + return userMetadataDAO.update(UserMetadataEntity.fromMemberEntity(member)) + .map(updated -> member); + }); + + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/PreUserService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/PreUserService.java new file mode 100644 index 0000000..8584a95 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/PreUserService.java @@ -0,0 +1,83 @@ +package net.miarma.api.microservices.huertos.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +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.microservices.huertos.dao.PreUserDAO; +import net.miarma.api.microservices.huertos.entities.PreUserEntity; +import net.miarma.api.microservices.huertos.validators.PreUserValidator; + +import java.util.List; + +public class PreUserService { + + private final PreUserDAO preUserDAO; + private final PreUserValidator preUserValidator; + + public PreUserService(Pool pool) { + this.preUserDAO = new PreUserDAO(pool); + this.preUserValidator = new PreUserValidator(); + } + + public Future> getAll(QueryParams params) { + return preUserDAO.getAll(params); + } + + public Future getById(Integer id) { + return preUserDAO.getById(id).compose(preUser -> { + if (preUser == null) { + return Future.failedFuture(new NotFoundException("PreUser with id " + id)); + } + return Future.succeededFuture(preUser); + }); + } + + public Future getByRequestId(Integer requestId) { + return preUserDAO.getByRequestId(requestId).compose(preUser -> { + if (preUser == null) { + return Future.failedFuture(new NotFoundException("PreUser with request id " + requestId)); + } + return Future.succeededFuture(preUser); + }); + } + + public Future validatePreUser(String json) { + PreUserEntity preUser = Constants.GSON.fromJson(json, PreUserEntity.class); + return preUserValidator.validate(preUser, false).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return Future.succeededFuture(preUser); + }); + } + + public Future create(PreUserEntity preUser) { + return preUserValidator.validate(preUser, true).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return preUserDAO.insert(preUser); + }); + } + + public Future update(PreUserEntity preUser) { + return getById(preUser.getPre_user_id()).compose(existing -> preUserValidator.validate(preUser, true).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return preUserDAO.update(preUser); + })); + } + + public Future delete(Integer id) { + return getById(id).compose(preUser -> { + if (preUser == null) { + return Future.failedFuture(new NotFoundException("PreUser with id " + id)); + } + return preUserDAO.delete(id); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/ProfileService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/ProfileService.java new file mode 100644 index 0000000..a01070a --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/ProfileService.java @@ -0,0 +1,51 @@ +package net.miarma.api.microservices.huertos.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants.HuertosUserStatus; +import net.miarma.api.backlib.exceptions.UnauthorizedException; +import net.miarma.api.backlib.security.JWTManager; +import net.miarma.api.microservices.huertos.entities.ProfileDTO; + +public class ProfileService { + private final MemberService memberService; + private final RequestService requestService; + private final IncomeService incomeService; + + public ProfileService(Pool pool) { + this.memberService = new MemberService(pool); + this.requestService = new RequestService(pool); + this.incomeService = new IncomeService(pool); + } + + public Future getProfile(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + ProfileDTO dto = new ProfileDTO(); + + return memberService.getById(userId).compose(member -> { + if (member.getStatus() == HuertosUserStatus.INACTIVE) { + return Future.failedFuture(new UnauthorizedException("Member is inactive")); + } + + dto.setMember(member); + + return Future.all( + requestService.getMyRequests(token), + incomeService.getMyIncomes(token), + memberService.hasCollaborator(token), + requestService.hasCollaboratorRequest(token), + memberService.hasGreenHouse(token), + requestService.hasGreenHouseRequest(token) + ).map(f -> { + dto.setRequests(f.resultAt(0)); + dto.setPayments(f.resultAt(1)); + dto.setHasCollaborator(f.resultAt(2)); + dto.setHasCollaboratorRequest(f.resultAt(3)); + dto.setHasGreenHouse(f.resultAt(4)); + dto.setHasGreenHouseRequest(f.resultAt(5)); + return dto; + }); + + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/RequestService.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/RequestService.java new file mode 100644 index 0000000..902d442 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/services/RequestService.java @@ -0,0 +1,170 @@ +package net.miarma.api.microservices.huertos.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.Constants.HuertosRequestStatus; +import net.miarma.api.backlib.Constants.HuertosRequestType; +import net.miarma.api.backlib.Constants.HuertosUserStatus; +import net.miarma.api.backlib.Constants.HuertosUserType; +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.security.JWTManager; +import net.miarma.api.microservices.huertos.dao.RequestDAO; +import net.miarma.api.microservices.huertos.entities.RequestEntity; +import net.miarma.api.microservices.huertos.entities.ViewRequestsWithPreUsers; +import net.miarma.api.microservices.huertos.validators.RequestValidator; + +import java.util.List; + +public class RequestService { + + private final RequestDAO requestDAO; + private final RequestValidator requestValidator; + private final PreUserService preUserService; + private final MemberService memberService; + + public RequestService(Pool pool) { + this.requestDAO = new RequestDAO(pool); + this.requestValidator = new RequestValidator(); + this.preUserService = new PreUserService(pool); + this.memberService = new MemberService(pool); + } + + public Future> getAll() { + return requestDAO.getAll(); + } + + public Future> getAll(QueryParams params) { + return requestDAO.getAll(params); + } + + public Future getById(Integer id) { + return requestDAO.getById(id).compose(request -> { + if (request == null) { + return Future.failedFuture(new NotFoundException("Request with id " + id)); + } + return Future.succeededFuture(request); + }); + } + + public Future> getRequestsWithPreUsers() { + return requestDAO.getRequestsWithPreUsers(); + } + + public Future getRequestWithPreUserById(Integer id) { + return requestDAO.getRequestWithPreUserById(id).compose(request -> { + if (request == null) { + return Future.failedFuture(new NotFoundException("Request with id " + id)); + } + return Future.succeededFuture(request); + }); + } + + public Future getRequestCount() { + return requestDAO.getAll().compose(requests -> Future.succeededFuture(requests.stream() + .filter(r -> r.getStatus() == HuertosRequestStatus.PENDING) + .mapToInt(r -> 1) + .sum())); + } + + public Future> getMyRequests(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + return requestDAO.getByUserId(userId).compose(Future::succeededFuture); + } + + public Future hasCollaboratorRequest(String token) { + return getMyRequests(token).compose(requests -> { + boolean result = requests.stream() + .filter(r -> r.getStatus() == HuertosRequestStatus.PENDING) + .anyMatch(r -> r.getType() == HuertosRequestType.ADD_COLLABORATOR); + return Future.succeededFuture(result); + }); + } + + public Future hasGreenHouseRequest(String token) { + return getMyRequests(token).compose(requests -> Future.succeededFuture(requests.stream() + .filter(r -> r.getStatus() == HuertosRequestStatus.PENDING) + .anyMatch(r -> r.getType() == HuertosRequestType.ADD_GREENHOUSE))); + } + + public Future create(RequestEntity request) { + return requestValidator.validate(request).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return requestDAO.insert(request); + }); + } + + public Future update(RequestEntity request) { + return getById(request.getRequest_id()).compose(existing -> requestValidator.validate(request).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + return requestDAO.update(request); + })); + } + + public Future delete(Integer id) { + return getById(id).compose(request -> { + if (request == null) { + return Future.failedFuture(new NotFoundException("Request with id " + id)); + } + return requestDAO.delete(id); + }); + } + + public Future acceptRequest(Integer id) { + RequestEntity request = new RequestEntity(); + request.setRequest_id(id); + request.setStatus(HuertosRequestStatus.APPROVED); + + return requestDAO.update(request).compose(updatedRequest -> getById(id).compose(fullRequest -> { + HuertosRequestType type = fullRequest.getType(); + + return switch (type) { + case ADD_COLLABORATOR, REGISTER -> preUserService.getByRequestId(id).compose(preUser -> { + if (preUser == null) { + return Future.failedFuture("PreUser not found for request id " + id); + } + + return memberService.createFromPreUser(preUser).compose(createdUser -> + preUserService.delete(preUser.getPre_user_id()).map(v -> fullRequest) + ); + }); + case UNREGISTER -> + memberService.changeMemberStatus(fullRequest.getRequested_by(), HuertosUserStatus.INACTIVE) + .map(v -> fullRequest); + case REMOVE_COLLABORATOR -> + memberService.getById(fullRequest.getRequested_by()).compose(requestingMember -> { + Integer plotNumber = requestingMember.getPlot_number(); + + return memberService.getCollaborator(plotNumber).compose(collaborator -> { + if (collaborator == null) { + return Future.failedFuture("No collaborator found for plot number " + plotNumber); + } + + return memberService.changeMemberStatus(collaborator.getUser_id(), HuertosUserStatus.INACTIVE) + .map(v -> fullRequest); + }); + }); + case ADD_GREENHOUSE -> + memberService.changeMemberType(fullRequest.getRequested_by(), HuertosUserType.WITH_GREENHOUSE) + .map(v -> fullRequest); + case REMOVE_GREENHOUSE -> + memberService.changeMemberType(fullRequest.getRequested_by(), HuertosUserType.MEMBER) + .map(v -> fullRequest); + }; + })); + } + + public Future rejectRequest(Integer id) { + RequestEntity request = new RequestEntity(); + request.setRequest_id(id); + request.setStatus(HuertosRequestStatus.REJECTED); + return requestDAO.update(request); + } + +} \ No newline at end of file diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/AnnouncementValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/AnnouncementValidator.java new file mode 100644 index 0000000..ca16fae --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/AnnouncementValidator.java @@ -0,0 +1,33 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.AnnouncementEntity; + +public class AnnouncementValidator { + + public Future validate(AnnouncementEntity announce) { + ValidationResult result = new ValidationResult(); + + if (announce == null) { + result.addError("announce", "El anuncio no puede ser nulo"); + return Future.succeededFuture(result); + } + + if (announce.getBody() == null || announce.getBody().isBlank()) { + result.addError("body", "El cuerpo del anuncio es obligatorio"); + } else if (announce.getBody().length() > 1000) { + result.addError("body", "El cuerpo del anuncio no puede exceder los 1000 caracteres"); + } + + if (announce.getPriority() == null) { + result.addError("priority", "La prioridad del anuncio es obligatoria"); + } + + if (announce.getPublished_by() == null || announce.getPublished_by() <= 0) { + result.addError("published_by", "El ID del usuario que publica el anuncio es obligatorio y debe ser mayor que 0"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/BalanceValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/BalanceValidator.java new file mode 100644 index 0000000..a653794 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/BalanceValidator.java @@ -0,0 +1,35 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.backlib.validation.Validator; +import net.miarma.api.microservices.huertos.entities.BalanceEntity; + +import java.math.BigDecimal; + +public class BalanceValidator implements Validator { + + @Override + public Future validate(BalanceEntity balance) { + ValidationResult result = new ValidationResult(); + + if (balance == null) { + result.addError("balance", "Balance data is null"); + return Future.succeededFuture(result); + } + + if (balance.getInitial_bank() == null) { + result.addError("initial_bank", "Debe proporcionar el saldo inicial en banco."); + } else if (balance.getInitial_bank().compareTo(BigDecimal.ZERO) < 0) { + result.addError("initial_bank", "El saldo inicial en banco no puede ser negativo."); + } + + if (balance.getInitial_cash() == null) { + result.addError("initial_cash", "Debe proporcionar el saldo inicial en efectivo."); + } else if (balance.getInitial_cash().compareTo(BigDecimal.ZERO) < 0) { + result.addError("initial_cash", "El saldo inicial en efectivo no puede ser negativo."); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/ExpenseValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/ExpenseValidator.java new file mode 100644 index 0000000..37609dd --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/ExpenseValidator.java @@ -0,0 +1,45 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.ExpenseEntity; + +public class ExpenseValidator { + + public Future validate(ExpenseEntity expense) { + ValidationResult result = new ValidationResult(); + + if (expense == null) { + result.addError("expense", "La entidad no puede ser nula"); + return Future.succeededFuture(result); + } + + if (expense.getConcept() == null || expense.getConcept().isBlank()) { + result.addError("concept", "El concepto es obligatorio"); + } + + if (expense.getAmount() == null) { + result.addError("amount", "El importe es obligatorio"); + } else if (expense.getAmount().signum() < 0) { + result.addError("amount", "El importe no puede ser negativo"); + } + + if(expense.getSupplier() == null || expense.getSupplier().isBlank()) { + result.addError("supplier", "El proveedor es obligatorio"); + } else if (expense.getSupplier() != null && expense.getSupplier().length() > 255) { + result.addError("supplier", "El nombre del proveedor es demasiado largo"); + } + + if(expense.getInvoice() == null || expense.getInvoice().isBlank()) { + result.addError("invoice", "El nombre de la factura es obligatorio"); + } else if (expense.getInvoice() != null && expense.getInvoice().length() > 255) { + result.addError("invoice", "El nombre de la factura es demasiado largo"); + } + + if (expense.getType() == null) { + result.addError("type", "El tipo de pago es obligatorio"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/IncomeValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/IncomeValidator.java new file mode 100644 index 0000000..8e433fa --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/IncomeValidator.java @@ -0,0 +1,41 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.IncomeEntity; + +public class IncomeValidator { + + public Future validate(IncomeEntity income) { + ValidationResult result = new ValidationResult(); + + if (income == null) { + result.addError("income", "La entidad no puede ser nula"); + return Future.succeededFuture(result); + } + + if (income.getMember_number() == null || income.getMember_number() <= 0) { + result.addError("member_number", "El número de socio es obligatorio y debe ser mayor que 0"); + } + + if (income.getConcept() == null || income.getConcept().isBlank()) { + result.addError("concept", "El concepto es obligatorio"); + } + + if (income.getAmount() == null) { + result.addError("amount", "El importe es obligatorio"); + } else if (income.getAmount().signum() < 0) { + result.addError("amount", "El importe no puede ser negativo"); + } + + if (income.getType() == null) { + result.addError("type", "El tipo de pago es obligatorio"); + } + + if (income.getFrequency() == null) { + result.addError("frequency", "La frecuencia del pago es obligatoria"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/MemberValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/MemberValidator.java new file mode 100644 index 0000000..8695f30 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/MemberValidator.java @@ -0,0 +1,57 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.security.DNIValidator; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.MemberEntity; + +public class MemberValidator { + + public Future validate(MemberEntity member) { + ValidationResult result = new ValidationResult(); + + if (member == null) { + result.addError("member", "La entidad no puede ser nula"); + return Future.succeededFuture(result); + } + + if (member.getDni() == null || member.getDni().isBlank()) { + result.addError("dni", "El DNI es obligatorio"); + } else if (!DNIValidator.isValid(member.getDni())) { + result.addError("dni", "El DNI no es válido"); + } + + if (member.getDisplay_name() == null || member.getDisplay_name().isBlank()) { + result.addError("display_name", "El nombre es obligatorio"); + } + + if (member.getPhone() == null || member.getPhone() <= 0 || + member.getPhone().toString().length() != 9) { + result.addError("phone", "El teléfono es obligatorio y debe ser válido"); + } + + if (member.getEmail() != null && !member.getEmail().isBlank()) { + if (!member.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) { + result.addError("email", "El correo electrónico no es válido"); + } + } + + if (member.getUser_name() == null || member.getUser_name().isBlank()) { + result.addError("user_name", "El nombre de usuario es obligatorio"); + } + + if (member.getType() == null) { + result.addError("type", "El tipo de usuario es obligatorio"); + } + + if (member.getStatus() == null) { + result.addError("status", "El estado del usuario es obligatorio"); + } + + if (member.getRole() == null) { + result.addError("role", "El rol del usuario en Huertos es obligatorio"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/PreUserValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/PreUserValidator.java new file mode 100644 index 0000000..6958934 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/PreUserValidator.java @@ -0,0 +1,69 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.security.DNIValidator; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.PreUserEntity; + +public class PreUserValidator { + + public Future validate(PreUserEntity preUser, boolean checkRequestId) { + ValidationResult result = new ValidationResult(); + + if (preUser == null) { + result.addError("preUser", "El preusuario no puede ser nulo"); + return Future.succeededFuture(result); + } + + if (preUser.getDni() == null || preUser.getDni().isBlank()) { + result.addError("dni", "El DNI es obligatorio"); + } else if (!DNIValidator.isValid(preUser.getDni())) { + result.addError("dni", "El DNI no es válido"); + } + + if (preUser.getDisplay_name() == null || preUser.getDisplay_name().isBlank()) { + result.addError("display_name", "El nombre es obligatorio"); + } + + if (preUser.getUser_name() == null || preUser.getUser_name().isBlank()) { + result.addError("user_name", "El nombre de usuario es obligatorio"); + } + + if (preUser.getEmail() != null && !preUser.getEmail().isBlank()) { + if (!preUser.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) { + result.addError("email", "El correo electrónico no es válido"); + } + } + + if (preUser.getPhone() == null || preUser.getPhone() <= 0 || + preUser.getPhone().toString().length() != 9) { + result.addError("phone", "El teléfono es obligatorio y debe ser válido"); + } + + if (preUser.getAddress() == null || preUser.getAddress().isBlank()) { + result.addError("address", "La dirección es obligatoria"); + } + + if (preUser.getZip_code() == null || preUser.getZip_code().isBlank() || preUser.getZip_code().length() != 5) { + result.addError("zip_code", "El código postal es obligatorio"); + } + + if (preUser.getCity() == null || preUser.getCity().isBlank()) { + result.addError("city", "La ciudad es obligatoria"); + } + + if (preUser.getType() == null) { + result.addError("type", "El tipo de usuario es obligatorio"); + } + + if (preUser.getStatus() == null) { + result.addError("status", "El estado del usuario es obligatorio"); + } + + if (checkRequestId && preUser.getRequest_id() == null) { + result.addError("request_id", "El ID de solicitud es obligatorio"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/RequestValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/RequestValidator.java new file mode 100644 index 0000000..e0fe3cf --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/RequestValidator.java @@ -0,0 +1,32 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.Constants.HuertosRequestType; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.RequestEntity; + +public class RequestValidator { + + public Future validate(RequestEntity request) { + ValidationResult result = new ValidationResult(); + + if (request == null) { + result.addError("request", "La solicitud no puede ser nula"); + return Future.succeededFuture(result); + } + + if (request.getType() == null) { + result.addError("type", "El tipo de solicitud es obligatorio"); + } + + if (request.getStatus() == null) { + result.addError("status", "El estado de la solicitud es obligatorio"); + } + + if (request.getRequested_by() == null && request.getType() != HuertosRequestType.REGISTER) { + result.addError("requested_by", "El solicitante es obligatorio"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/UserMetadataValidator.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/UserMetadataValidator.java new file mode 100644 index 0000000..947644c --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/validators/UserMetadataValidator.java @@ -0,0 +1,43 @@ +package net.miarma.api.microservices.huertos.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.huertos.entities.UserMetadataEntity; + +public class UserMetadataValidator { + + public Future validate(UserMetadataEntity meta) { + ValidationResult result = new ValidationResult(); + + if (meta == null) { + result.addError("metadata", "Los metadatos no pueden ser nulos"); + return Future.succeededFuture(result); + } + + if (meta.getUser_id() == null) + result.addError("user_id", "El ID de usuario es obligatorio"); + + if (meta.getMember_number() == null || meta.getMember_number() <= 0) + result.addError("member_number", "El número de miembro debe ser mayor que 0"); + + if (meta.getPlot_number() == null || meta.getPlot_number() <= 0) + result.addError("plot_number", "El número de parcela debe ser mayor que 0"); + + if (meta.getDni() == null || meta.getDni().isBlank()) + result.addError("dni", "El DNI es obligatorio"); + + if (meta.getPhone() == null || meta.getPhone() <= 0) + result.addError("phone", "El número de teléfono debe ser válido"); + + if (meta.getType() == null) + result.addError("type", "El tipo de usuario es obligatorio"); + + if (meta.getStatus() == null) + result.addError("status", "El estado del usuario es obligatorio"); + + if (meta.getRole() == null) + result.addError("role", "El rol del usuario es obligatorio"); + + return Future.succeededFuture(result); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosDataVerticle.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosDataVerticle.java new file mode 100644 index 0000000..5ebd6e0 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosDataVerticle.java @@ -0,0 +1,266 @@ +package net.miarma.api.microservices.huertos.verticles; + +import java.util.stream.Collectors; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.Constants.HuertosUserStatus; +import net.miarma.api.backlib.Constants.HuertosUserType; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.EventBusUtil; +import net.miarma.api.backlib.util.NameCensorer; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.huertos.routing.HuertosDataRouter; +import net.miarma.api.microservices.huertos.services.BalanceService; +import net.miarma.api.microservices.huertos.services.IncomeService; +import net.miarma.api.microservices.huertos.services.MemberService; +import net.miarma.api.microservices.huertos.services.PreUserService; +import net.miarma.api.microservices.huertos.services.ProfileService; +import net.miarma.api.microservices.huertos.services.RequestService; + +public class HuertosDataVerticle extends AbstractVerticle { + + private ConfigManager configManager; + private MemberService memberService; + private IncomeService incomeService; + private BalanceService balanceService; + private RequestService requestService; + private PreUserService preUserService; + private ProfileService profileService; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + + memberService = new MemberService(pool); + incomeService = new IncomeService(pool); + balanceService = new BalanceService(pool); + requestService = new RequestService(pool); + preUserService = new PreUserService(pool); + profileService = new ProfileService(pool); + + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + + HuertosDataRouter.mount(router, vertx, pool); + registerLogicVerticleConsumer(); + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("huertos.data.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } + + private void registerLogicVerticleConsumer() { + vertx.eventBus().consumer(Constants.HUERTOS_EVENT_BUS, message -> { + JsonObject body = (JsonObject) message.body(); + String action = body.getString("action"); + + switch (action) { + case "login" -> { + String email = body.getString("email", null); + String userName = body.getString("userName", null); + String password = body.getString("password"); + boolean keepLoggedIn = body.getBoolean("keepLoggedIn", false); + + memberService.login(email != null ? email : userName, password, keepLoggedIn) + .onSuccess(message::reply) + .onFailure(EventBusUtil.fail(message)); + } + + case "getByMemberNumber" -> + memberService.getByMemberNumber(body.getInteger("memberNumber")) + .onSuccess(member -> message.reply(new JsonObject(Constants.GSON.toJson(member)))) + .onFailure(EventBusUtil.fail(message)); + + + case "getByPlotNumber" -> + memberService.getByPlotNumber(body.getInteger("plotNumber")) + .onSuccess(member -> message.reply(new JsonObject(Constants.GSON.toJson(member)))) + .onFailure(EventBusUtil.fail(message)); + + + case "getByDNI" -> + memberService.getByDni(body.getString("dni")) + .onSuccess(member -> message.reply(new JsonObject(Constants.GSON.toJson(member)))) + .onFailure(EventBusUtil.fail(message)); + + case "getUserPayments" -> incomeService.getUserPayments(body.getInteger("memberNumber")) + .onSuccess(payments -> { + String paymentsJson = payments.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(paymentsJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "hasPaid" -> incomeService.hasPaid(body.getInteger("memberNumber")) + .onSuccess(result -> message.reply(new JsonObject().put("hasPaid", result))) + .onFailure(EventBusUtil.fail(message)); + + case "getWaitlist" -> + memberService.getWaitlist() + .onSuccess(list -> { + String listJson = list.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(listJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getLimitedWaitlist" -> + memberService.getWaitlist() + .onSuccess(list -> { + String listJson = list.stream() + .map(member -> { + JsonObject json = new JsonObject(Constants.GSON.toJson(member)); + json.put("display_name", NameCensorer.censor(json.getString("display_name"))); + json.remove("user_id"); + json.remove("member_number"); + json.remove("plot_number"); + json.remove("dni"); + json.remove("phone"); + json.remove("email"); + json.remove("user_name"); + json.remove("notes"); + json.remove("type"); + json.remove("status"); + json.remove("role"); + json.remove("global_status"); + json.remove("global_role"); + return json; + }) + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(listJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getLastMemberNumber" -> memberService.getLastMemberNumber() + .onSuccess(last -> message.reply(new JsonObject().put("lastMemberNumber", last))) + .onFailure(EventBusUtil.fail(message)); + + case "getBalanceWithTotals" -> balanceService.getBalanceWithTotals() + .onSuccess(balance -> { + String balanceJson = Constants.GSON.toJson(balance); + message.reply(new JsonObject(balanceJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getRequestsWithPreUsers" -> requestService.getRequestsWithPreUsers() + .onSuccess(requests -> { + String requestsJson = requests.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(requestsJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getRequestWithPreUser" -> requestService.getRequestWithPreUserById(body.getInteger("requestId")) + .onSuccess(request -> { + String requestJson = Constants.GSON.toJson(request); + message.reply(new JsonObject(requestJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getProfile" -> profileService.getProfile(body.getString("token")) + .onSuccess(profile -> { + String profileJson = Constants.GSON.toJson(profile); + message.reply(new JsonObject(profileJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getRequestCount" -> requestService.getRequestCount() + .onSuccess(count -> message.reply(new JsonObject().put("count", count))) + .onFailure(EventBusUtil.fail(message)); + + case "getMyIncomes" -> incomeService.getMyIncomes(body.getString("token")) + .onSuccess(incomes -> { + String incomesJson = incomes.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(incomesJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "getMyRequests" -> requestService.getMyRequests(body.getString("token")) + .onSuccess(requests -> { + String requestsJson = requests.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(requestsJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "hasCollaborator" -> memberService.hasCollaborator(body.getString("token")) + .onSuccess(result -> message.reply(new JsonObject().put("hasCollaborator", result))) + .onFailure(EventBusUtil.fail(message)); + + case "hasCollaboratorRequest" -> requestService.hasCollaboratorRequest(body.getString("token")) + .onSuccess(result -> message.reply(new JsonObject().put("hasCollaboratorRequest", result))) + .onFailure(EventBusUtil.fail(message)); + + case "hasGreenHouse" -> memberService.hasGreenHouse(body.getString("token")) + .onSuccess(result -> message.reply(new JsonObject().put("hasGreenHouse", result))) + .onFailure(EventBusUtil.fail(message)); + + case "hasGreenHouseRequest" -> requestService.hasGreenHouseRequest(body.getString("token")) + .onSuccess(result -> message.reply(new JsonObject().put("hasGreenHouseRequest", result))) + .onFailure(EventBusUtil.fail(message)); + + case "acceptRequest" -> requestService.acceptRequest(body.getInteger("requestId")) + .onSuccess(request -> { + String requestJson = Constants.GSON.toJson(request); + message.reply(new JsonObject(requestJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "rejectRequest" -> requestService.rejectRequest(body.getInteger("requestId")) + .onSuccess(request -> { + String requestJson = Constants.GSON.toJson(request); + message.reply(new JsonObject(requestJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + case "changeMemberStatus" -> { + HuertosUserStatus status = HuertosUserStatus.fromInt(body.getInteger("status")); + memberService.changeMemberStatus(body.getInteger("memberNumber"), status) + .onSuccess(member -> { + String memberJson = Constants.GSON.toJson(member); + message.reply(new JsonObject(memberJson)); + }) + .onFailure(EventBusUtil.fail(message)); + } + + case "changeMemberType" -> { + HuertosUserType type = HuertosUserType.fromInt(body.getInteger("type")); + memberService.changeMemberType(body.getInteger("memberNumber"), type) + .onSuccess(member -> { + String memberJson = Constants.GSON.toJson(member); + message.reply(new JsonObject(memberJson)); + }) + .onFailure(EventBusUtil.fail(message)); + } + + case "validatePreUser" -> preUserService.validatePreUser(body.getString("preUser")) + .onSuccess(preUser -> { + String preUserJson = Constants.GSON.toJson(preUser); + message.reply(new JsonObject(preUserJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + default -> EventBusUtil.fail(message).handle(new IllegalArgumentException("Unknown action: " + action)); + } + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosLogicVerticle.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosLogicVerticle.java new file mode 100644 index 0000000..80dfc58 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosLogicVerticle.java @@ -0,0 +1,32 @@ +package net.miarma.api.microservices.huertos.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.huertos.routing.HuertosLogicRouter; + +public class HuertosLogicVerticle extends AbstractVerticle{ + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + HuertosLogicRouter.mount(router, vertx, pool); + + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("huertos.logic.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } +} diff --git a/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosMainVerticle.java b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosMainVerticle.java new file mode 100644 index 0000000..0ce67f2 --- /dev/null +++ b/microservices/huertos/src/main/java/net/miarma/api/microservices/huertos/verticles/HuertosMainVerticle.java @@ -0,0 +1,58 @@ +package net.miarma.api.microservices.huertos.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.LogAccumulator; +import net.miarma.api.backlib.util.DeploymentUtil; + +public class HuertosMainVerticle extends AbstractVerticle { + + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + try { + this.configManager = ConfigManager.getInstance(); + deployVerticles(); + startPromise.complete(); + } catch (Exception e) { + Constants.LOGGER.error(DeploymentUtil.failMessage(HuertosMainVerticle.class, e)); + startPromise.fail(e); + } + } + + private void deployVerticles() { + vertx.deployVerticle(new HuertosDataVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(HuertosDataVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("huertos.data.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(HuertosDataVerticle.class, result.cause())); + } + }); + + vertx.deployVerticle(new HuertosLogicVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(HuertosLogicVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("huertos.logic.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(HuertosLogicVerticle.class, result.cause())); + } + }); + } + +} diff --git a/microservices/huertosdecine/.gitignore b/microservices/huertosdecine/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/microservices/huertosdecine/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/microservices/huertosdecine/pom.xml b/microservices/huertosdecine/pom.xml new file mode 100644 index 0000000..553bfb9 --- /dev/null +++ b/microservices/huertosdecine/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + net.miarma.api + huertosdecine + 1.2.0 + + + 23 + 23 + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + net.miarma.api + backlib + 1.2.0 + + + + + ME-HuertosDeCine + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.api.microservices.huertosdecine.CineMainVerticle + + + + + + + + + + diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/MovieDAO.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/MovieDAO.java new file mode 100644 index 0000000..4c7ccd8 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/MovieDAO.java @@ -0,0 +1,155 @@ +package net.miarma.api.microservices.huertosdecine.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.microservices.huertosdecine.entities.MovieEntity; + +import java.util.List; +import java.util.Map; + +public class MovieDAO implements DataAccessObject { + + private final DatabaseManager db; + + public MovieDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MovieEntity.class) + .where(Map.of("movie_id", id.toString())) + .build(); + + db.executeOne(query, MovieEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(MovieEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, MovieEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(MovieEntity movieEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .insert(movieEntity) + .build(); + + db.executeOne(query, MovieEntity.class, + _ -> promise.complete(movieEntity), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(MovieEntity movieEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .upsert(movieEntity, conflictKeys) + .build(); + + db.executeOne(query, MovieEntity.class, + _ -> promise.complete(movieEntity), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(MovieEntity movieEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .update(movieEntity) + .build(); + + db.executeOne(query, MovieEntity.class, + _ -> promise.complete(movieEntity), + promise::fail + ); + + return promise.future(); + } + + public Future updateWithNulls(MovieEntity movieEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .updateWithNulls(movieEntity) + .build(); + + db.executeOne(query, MovieEntity.class, + _ -> promise.complete(movieEntity), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + MovieEntity movieEntity = new MovieEntity(); + movieEntity.setMovie_id(id); + + String query = QueryBuilder + .delete(movieEntity) + .build(); + + db.executeOne(query, MovieEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(MovieEntity.class) + .where( Map.of("movie_id", id.toString())) + .build(); + + db.executeOne(query, MovieEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/UserMetadataDAO.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/UserMetadataDAO.java new file mode 100644 index 0000000..99a9029 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/UserMetadataDAO.java @@ -0,0 +1,151 @@ +package net.miarma.api.microservices.huertosdecine.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.microservices.huertosdecine.entities.UserMetadataEntity; + +import java.util.List; +import java.util.Map; + +public class UserMetadataDAO implements DataAccessObject { + + private final DatabaseManager db; + + public UserMetadataDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, UserMetadataEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(UserMetadataEntity metadata) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .insert(metadata) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + _ -> promise.complete(), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(UserMetadataEntity metadata, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .upsert(metadata, conflictKeys) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + _ -> promise.complete(metadata), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(UserMetadataEntity metadata) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .update(metadata) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + _ -> promise.complete(metadata), + promise::fail + ); + + return promise.future(); + } + + public Future updateWithNulls(UserMetadataEntity metadata) { + Promise promise = Promise.promise(); + String query = QueryBuilder.updateWithNulls(metadata).build(); + + db.executeOne(query, UserMetadataEntity.class, + _ -> promise.complete(metadata), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + UserMetadataEntity metadata = new UserMetadataEntity(); + metadata.setUser_id(id); + + String query = QueryBuilder.delete(metadata).build(); + + db.executeOne(query, UserMetadataEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/ViewerDAO.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/ViewerDAO.java new file mode 100644 index 0000000..139aa35 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/ViewerDAO.java @@ -0,0 +1,98 @@ +package net.miarma.api.microservices.huertosdecine.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.microservices.huertosdecine.entities.ViewerEntity; + +import java.util.List; +import java.util.Map; + +public class ViewerDAO implements DataAccessObject { + + private final DatabaseManager db; + + public ViewerDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ViewerEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, ViewerEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(ViewerEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, ViewerEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(ViewerEntity viewerEntity) { + throw new UnsupportedOperationException("Insert not supported on view-based DAO"); + } + + @Override + public Future upsert(ViewerEntity viewer, String... conflictKeys) { + throw new UnsupportedOperationException("Upsert not supported on view-based DAO"); + } + + @Override + public Future update(ViewerEntity viewerEntity) { + throw new UnsupportedOperationException("Update not supported on view-based DAO"); + } + + @Override + public Future delete(Integer id) { + throw new UnsupportedOperationException("Delete not supported on view-based DAO"); + } + + @Override + public Future exists(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ViewerEntity.class) + .where(Map.of("user_id", integer.toString())) + .build(); + + db.executeOne(query, ViewerEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/VoteDAO.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/VoteDAO.java new file mode 100644 index 0000000..2ea0cfc --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/dao/VoteDAO.java @@ -0,0 +1,185 @@ +package net.miarma.api.microservices.huertosdecine.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.microservices.huertosdecine.entities.VoteEntity; + +import java.util.List; +import java.util.Map; + +public class VoteDAO implements DataAccessObject { + + private final DatabaseManager db; + + public VoteDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(VoteEntity.class) + .where(Map.of("movie_id", integer.toString())) + .build(); + + db.executeOne(query, VoteEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(VoteEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, VoteEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + public Future> getVotesByMovieId(Integer movieId) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(VoteEntity.class) + .where(Map.of("movie_id", movieId.toString())) + .build(); + + db.execute(query, VoteEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(VoteEntity voteEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .insert(voteEntity) + .build(); + + db.executeOne(query, VoteEntity.class, + _ -> promise.complete(voteEntity), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(VoteEntity voteEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .upsert(voteEntity, conflictKeys) + .build(); + + db.executeOne(query, VoteEntity.class, + _ -> promise.complete(voteEntity), + promise::fail + ); + + return promise.future(); + } + + public Future upsert(VoteEntity voteEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .upsert(voteEntity, "user_id", "movie_id") + .build(); + + db.executeOne(query, VoteEntity.class, + _ -> promise.complete(voteEntity), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(VoteEntity voteEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .update(voteEntity) + .build(); + + db.executeOne(query, VoteEntity.class, + _ -> promise.complete(voteEntity), + promise::fail + ); + + return promise.future(); + } + + public Future updateWithNulls(VoteEntity voteEntity) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .updateWithNulls(voteEntity) + .build(); + + db.executeOne(query, VoteEntity.class, + _ -> promise.complete(voteEntity), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + VoteEntity voteEntity = new VoteEntity(); + voteEntity.setMovie_id(id); + + String query = QueryBuilder + .delete(voteEntity) + .build(); + + db.executeOne(query, VoteEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future exists(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(VoteEntity.class) + .where(Map.of("movie_id", integer.toString())) + .build(); + + db.executeOne(query, VoteEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/MovieEntity.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/MovieEntity.java new file mode 100644 index 0000000..b807466 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/MovieEntity.java @@ -0,0 +1,53 @@ +package net.miarma.api.microservices.huertosdecine.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +@Table("cine_movies") +public class MovieEntity extends AbstractEntity { + private Integer movie_id; + private String title; + private String description; + private String cover; + + public MovieEntity() { + super(); + } + + public MovieEntity(Row row) { + super(row); + } + + public Integer getMovie_id() { + return movie_id; + } + + public void setMovie_id(Integer movie_id) { + this.movie_id = movie_id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCover() { + return cover; + } + + public void setCover(String cover) { + this.cover = cover; + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/UserMetadataEntity.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/UserMetadataEntity.java new file mode 100644 index 0000000..1008c85 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/UserMetadataEntity.java @@ -0,0 +1,65 @@ +package net.miarma.api.microservices.huertosdecine.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("cine_user_metadata") +public class UserMetadataEntity extends AbstractEntity { + private Integer user_id; + private Constants.CineUserStatus status; + private Constants.CineUserRole role; + private LocalDateTime created_at; + + public UserMetadataEntity() { + super(); + } + + public UserMetadataEntity(Row row) { + super(row); + } + + public Integer getUser_id() { + return user_id; + } + + public void setUser_id(Integer user_id) { + this.user_id = user_id; + } + + public Constants.CineUserStatus getStatus() { + return status; + } + + public void setStatus(Constants.CineUserStatus status) { + this.status = status; + } + + public Constants.CineUserRole getRole() { + return role; + } + + public void setRole(Constants.CineUserRole role) { + this.role = role; + } + + public LocalDateTime getCreated_at() { + return created_at; + } + + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + public static UserMetadataEntity fromViewerEntity(ViewerEntity viewer) { + UserMetadataEntity metadata = new UserMetadataEntity(); + metadata.setUser_id(viewer.getUser_id()); + metadata.setStatus(viewer.getStatus()); + metadata.setRole(viewer.getRole()); + metadata.setCreated_at(viewer.getCreated_at()); + return metadata; + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/ViewerEntity.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/ViewerEntity.java new file mode 100644 index 0000000..cc74e8b --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/ViewerEntity.java @@ -0,0 +1,135 @@ +package net.miarma.api.microservices.huertosdecine.entities; + +import java.time.LocalDateTime; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants; +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("v_cine_viewers") +public class ViewerEntity 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 Constants.CineUserStatus status; + private Constants.CineUserRole role; + private Constants.CoreUserGlobalStatus global_status; + private Constants.CoreUserRole global_role; + private LocalDateTime created_at; + + public ViewerEntity() { + super(); + } + + public ViewerEntity(Row row) { + super(row); + } + + public ViewerEntity(IUser user, UserMetadataEntity metadata) { + this.user_id = user.getUser_id(); + this.user_name = user.getUser_name(); + this.email = user.getEmail(); + this.display_name = user.getDisplay_name(); + this.password = user.getPassword(); + this.avatar = user.getAvatar(); + this.status = metadata.getStatus(); + this.role = metadata.getRole(); + this.global_status = user.getGlobal_status(); + this.global_role = user.getGlobal_role(); + } + + 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 Constants.CineUserStatus getStatus() { + return status; + } + + public void setStatus(Constants.CineUserStatus status) { + this.status = status; + } + + public Constants.CineUserRole getRole() { + return role; + } + + public void setRole(Constants.CineUserRole role) { + this.role = role; + } + + public Constants.CoreUserGlobalStatus getGlobal_status() { + return global_status; + } + + public void setGlobal_status(Constants.CoreUserGlobalStatus global_status) { + this.global_status = global_status; + } + + public Constants.CoreUserRole getGlobal_role() { + return global_role; + } + + public void setGlobal_role(Constants.CoreUserRole global_role) { + this.global_role = global_role; + } + + public LocalDateTime getCreated_at() { + return created_at; + } + + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/VoteEntity.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/VoteEntity.java new file mode 100644 index 0000000..515770c --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/entities/VoteEntity.java @@ -0,0 +1,31 @@ +package net.miarma.api.microservices.huertosdecine.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("cine_votes") +public class VoteEntity extends AbstractEntity { + private Integer user_id; + private Integer movie_id; + private Integer vote; + private LocalDateTime voted_at; + + public VoteEntity() { super(); } + + public VoteEntity(Row row) { super(row); } + + public Integer getUser_id() { return user_id; } + public void setUser_id(Integer user_id) { this.user_id = user_id; } + + public Integer getMovie_id() { return movie_id; } + public void setMovie_id(Integer movie_id) { this.movie_id = movie_id; } + + public Integer getVote() { return vote; } + public void setVote(Integer vote) { this.vote = vote; } + + public LocalDateTime getVoted_at() { return voted_at; } + public void setVoted_at(LocalDateTime voted_at) { this.voted_at = voted_at; } +} \ No newline at end of file diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/MovieDataHandler.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/MovieDataHandler.java new file mode 100644 index 0000000..b6e5125 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/MovieDataHandler.java @@ -0,0 +1,97 @@ +package net.miarma.api.microservices.huertosdecine.handlers; + +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.http.QueryParams; +import net.miarma.api.microservices.huertosdecine.entities.MovieEntity; +import net.miarma.api.microservices.huertosdecine.services.MovieService; +import net.miarma.api.backlib.util.JsonUtil; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class MovieDataHandler { + private final MovieService movieService; + + public MovieDataHandler(Pool pool) { + this.movieService = new MovieService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + movieService.getAll(params) + .onSuccess(movies -> JsonUtil.sendJson(ctx, ApiStatus.OK, movies)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer movieId = Integer.parseInt(ctx.pathParam("movie_id")); + + movieService.getById(movieId) + .onSuccess(movie -> JsonUtil.sendJson(ctx, ApiStatus.OK, movie)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + MovieEntity movie = Constants.GSON.fromJson(ctx.body().asString(), MovieEntity.class); + + movieService.create(movie) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + MovieEntity movieFromBody = Constants.GSON.fromJson(ctx.body().asString(), MovieEntity.class); + + movieService.getById(movieFromBody.getMovie_id()) + .onSuccess(existingMovie -> { + String newCover = movieFromBody.getCover(); + String oldCover = existingMovie.getCover(); + + if (newCover != null && !newCover.isEmpty() && !newCover.equals(oldCover)) { + String cineFiles = ConfigManager.getInstance().getFilesDir("cine"); + String filename = Paths.get(existingMovie.getCover()).getFileName().toString(); + Path fullPath = Paths.get(cineFiles, filename); + + ctx.vertx().fileSystem().delete(fullPath.toString(), fileRes -> { + if (fileRes.failed()) { + Constants.LOGGER.warn("No se pudo eliminar el archivo de portada: {}", fullPath, fileRes.cause()); + } + + movieService.update(movieFromBody) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + }); + } else { + movieService.update(movieFromBody) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + }) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.NOT_FOUND, null, "Película no encontrada")); + } + + public void delete(RoutingContext ctx) { + Integer movieId = Integer.parseInt(ctx.pathParam("movie_id")); + + movieService.getById(movieId).onSuccess(movie -> { + String cineFiles = ConfigManager.getInstance().getFilesDir("cine"); + String filename = Paths.get(movie.getCover()).getFileName().toString(); + Path fullPath = Paths.get(cineFiles, filename); + + ctx.vertx().fileSystem().delete(fullPath.toString(), fileRes -> { + if (fileRes.failed()) { + Constants.LOGGER.warn("No se pudo eliminar el archivo de portada: {}", fullPath, fileRes.cause()); + } + + movieService.delete(movieId) + .onSuccess(_ -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + }); + }).onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/ViewerDataHandler.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/ViewerDataHandler.java new file mode 100644 index 0000000..97ba41c --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/ViewerDataHandler.java @@ -0,0 +1,68 @@ +package net.miarma.api.microservices.huertosdecine.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.microservices.huertosdecine.entities.UserMetadataEntity; +import net.miarma.api.microservices.huertosdecine.entities.ViewerEntity; +import net.miarma.api.microservices.huertosdecine.services.ViewerService; +import net.miarma.api.backlib.util.JsonUtil; + +public class ViewerDataHandler { + private final ViewerService viewerService; + + public ViewerDataHandler(Pool pool) { + this.viewerService = new ViewerService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + viewerService.getAll(params) + .onSuccess(viewers -> JsonUtil.sendJson(ctx, ApiStatus.OK, viewers)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer viewerId = Integer.parseInt(ctx.pathParam("viewer_id")); + + viewerService.getById(viewerId) + .onSuccess(viewer -> JsonUtil.sendJson(ctx, ApiStatus.OK, viewer)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + ViewerEntity viewer = Constants.GSON.fromJson(ctx.body().asString(), ViewerEntity.class); + + viewerService.create(viewer) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void createMetadata(RoutingContext ctx) { + UserMetadataEntity userMetadata = Constants.GSON.fromJson(ctx.body().asString(), UserMetadataEntity.class); + + viewerService.createMetadata(userMetadata) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + ViewerEntity viewer = Constants.GSON.fromJson(ctx.body().asString(), ViewerEntity.class); + + viewerService.update(viewer) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer viewerId = Integer.parseInt(ctx.pathParam("user_id")); + + viewerService.delete(viewerId) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/ViewerLogicHandler.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/ViewerLogicHandler.java new file mode 100644 index 0000000..4fb901f --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/ViewerLogicHandler.java @@ -0,0 +1,47 @@ +package net.miarma.api.microservices.huertosdecine.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class ViewerLogicHandler { + private final Vertx vertx; + + public ViewerLogicHandler(Vertx vertx) { + this.vertx = vertx; + } + + public void getVotesOnMovieByUserId(RoutingContext ctx) { + Integer movieId = Integer.parseInt(ctx.request().getParam("movie_id")); + Integer userId = Integer.parseInt(ctx.request().getParam("viewer_id")); + JsonObject request = new JsonObject().put("action", "getVotesOnMovieByUserId").put("user_id", userId).put("movie_id", movieId); + vertx.eventBus().request(Constants.CINE_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "No votes found for this movie and viewer"); + }); + } + + 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.CINE_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(), "The viewer is inactive or banned"); + } + }); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/VoteLogicHandler.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/VoteLogicHandler.java new file mode 100644 index 0000000..4e86f8c --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/handlers/VoteLogicHandler.java @@ -0,0 +1,61 @@ +package net.miarma.api.microservices.huertosdecine.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.microservices.huertosdecine.entities.VoteEntity; +import net.miarma.api.backlib.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class VoteLogicHandler { + private final Vertx vertx; + + public VoteLogicHandler(Vertx vertx) { + this.vertx = vertx; + } + + public void getVotes(RoutingContext ctx) { + Integer movieId = Integer.parseInt(ctx.request().getParam("movie_id")); + JsonObject request = new JsonObject().put("action", "getVotes").put("movie_id", movieId); + vertx.eventBus().request(Constants.CINE_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "No votes found for this movie and viewer"); + }); + } + + public void addVote(RoutingContext ctx) { + Integer movieId = Integer.parseInt(ctx.request().getParam("movie_id")); + VoteEntity vote = Constants.GSON.fromJson(ctx.body().asString(), VoteEntity.class); + JsonObject request = new JsonObject() + .put("action", "addVote") + .put("user_id", vote.getUser_id()) + .put("movie_id", movieId) + .put("vote", vote.getVote()); + vertx.eventBus().request(Constants.CINE_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.CREATED, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Error adding vote for this movie"); + }); + } + + public void deleteVote(RoutingContext ctx) { + VoteEntity vote = Constants.GSON.fromJson(ctx.body().asString(), VoteEntity.class); + JsonObject request = new JsonObject() + .put("action", "deleteVote") + .put("user_id", vote.getUser_id()); + vertx.eventBus().request(Constants.CINE_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Error deleting vote for this movie"); + }); + } + + public void getVoteSelf(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "getVoteSelf").put("token", token); + vertx.eventBus().request(Constants.CINE_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "No vote found for this viewer"); + }); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineDataRouter.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineDataRouter.java new file mode 100644 index 0000000..830e878 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineDataRouter.java @@ -0,0 +1,39 @@ +package net.miarma.api.microservices.huertosdecine.routing; + +import io.vertx.core.Vertx; +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.CineUserRole; +import net.miarma.api.backlib.http.ApiResponse; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.security.SusPather; +import net.miarma.api.microservices.huertosdecine.handlers.MovieDataHandler; +import net.miarma.api.microservices.huertosdecine.handlers.ViewerDataHandler; +import net.miarma.api.microservices.huertosdecine.routing.middlewares.CineAuthGuard; +import net.miarma.api.microservices.huertosdecine.services.ViewerService; + +public class CineDataRouter { + public static void mount(Router router, Vertx vertx, Pool pool) { + MovieDataHandler hMovieData = new MovieDataHandler(pool); + ViewerDataHandler hViewerData = new ViewerDataHandler(pool); + ViewerService viewerService = new ViewerService(pool); + CineAuthGuard authGuard = new CineAuthGuard(viewerService); + + router.route().handler(BodyHandler.create()); + + router.get(CineEndpoints.MOVIES).handler(authGuard.check()).handler(hMovieData::getAll); + router.get(CineEndpoints.MOVIE).handler(authGuard.check()).handler(hMovieData::getById); + router.post(CineEndpoints.MOVIES).handler(authGuard.check(CineUserRole.ADMIN)).handler(hMovieData::create); + router.put(CineEndpoints.MOVIE).handler(authGuard.check(CineUserRole.ADMIN)).handler(hMovieData::update); + router.delete(CineEndpoints.MOVIE).handler(authGuard.check(CineUserRole.ADMIN)).handler(hMovieData::delete); + + router.get(CineEndpoints.VIEWERS).handler(authGuard.check(CineUserRole.ADMIN)).handler(hViewerData::getAll); + router.get(CineEndpoints.VIEWER).handler(authGuard.check(CineUserRole.ADMIN)).handler(hViewerData::getById); + router.post(CineEndpoints.VIEWERS).handler(authGuard.check(CineUserRole.ADMIN)).handler(hViewerData::create); + router.put(CineEndpoints.VIEWER).handler(authGuard.check(CineUserRole.ADMIN)).handler(hViewerData::update); + router.delete(CineEndpoints.VIEWER).handler(authGuard.check(CineUserRole.ADMIN)).handler(hViewerData::delete); + router.post(CineEndpoints.VIEWER_METADATA).handler(authGuard.check(CineUserRole.ADMIN)).handler(hViewerData::createMetadata); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineEndpoints.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineEndpoints.java new file mode 100644 index 0000000..114f5dc --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineEndpoints.java @@ -0,0 +1,20 @@ +package net.miarma.api.microservices.huertosdecine.routing; + +import net.miarma.api.backlib.Constants; + +public class CineEndpoints { + /* OK */ public static final String LOGIN = Constants.CINE_PREFIX + "/login"; + + /* OK */ public static final String MOVIES = Constants.CINE_PREFIX + "/movies"; // GET, POST + /* OK */ public static final String MOVIE = Constants.CINE_PREFIX + "/movies/:movie_id"; // GET, PUT, DELETE + + /* OK */ public static final String VIEWERS = Constants.CINE_PREFIX + "/viewers"; // GET, POST, PUT, DELETE + /* OK */ public static final String VIEWER = Constants.CINE_PREFIX + "/viewers/:viewer_id"; // GET, PUT, DELETE + public static final String VIEWER_METADATA = Constants.CINE_PREFIX + "/viewers/metadata"; // POST, (PUT) + + // logic layer + /* OK */ public static final String MOVIE_VOTES = Constants.CINE_PREFIX + "/movies/:movie_id/votes"; // GET, POST, PUT, DELETE + /* OK */ public static final String SELF_VOTES = Constants.CINE_PREFIX + "/movies/votes/self"; // GET + /* OK */ public static final String VIEWER_VOTES_BY_MOVIE = Constants.CINE_PREFIX + "/viewers/:viewer_id/votes/:movie_id"; // GET + +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineLogicRouter.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineLogicRouter.java new file mode 100644 index 0000000..cb647bc --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/CineLogicRouter.java @@ -0,0 +1,32 @@ +package net.miarma.api.microservices.huertosdecine.routing; + +import io.vertx.core.Vertx; +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.http.ApiResponse; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.security.SusPather; +import net.miarma.api.microservices.huertosdecine.handlers.ViewerLogicHandler; +import net.miarma.api.microservices.huertosdecine.handlers.VoteLogicHandler; +import net.miarma.api.microservices.huertosdecine.routing.middlewares.CineAuthGuard; +import net.miarma.api.microservices.huertosdecine.services.ViewerService; + +public class CineLogicRouter { + public static void mount(Router router, Vertx vertx, Pool pool) { + ViewerLogicHandler hViewerLogic = new ViewerLogicHandler(vertx); + VoteLogicHandler hVoteLogic = new VoteLogicHandler(vertx); + ViewerService viewerService = new ViewerService(pool); + CineAuthGuard authGuard = new CineAuthGuard(viewerService); + + router.route().handler(BodyHandler.create()); + + router.post(CineEndpoints.LOGIN).handler(hViewerLogic::login); + router.get(CineEndpoints.VIEWER_VOTES_BY_MOVIE).handler(authGuard.check()).handler(hViewerLogic::getVotesOnMovieByUserId); + router.get(CineEndpoints.MOVIE_VOTES).handler(authGuard.check()).handler(hVoteLogic::getVotes); + router.post(CineEndpoints.MOVIE_VOTES).handler(authGuard.check()).handler(hVoteLogic::addVote); + router.delete(CineEndpoints.MOVIE_VOTES).handler(authGuard.check()).handler(hVoteLogic::deleteVote); + router.get(CineEndpoints.SELF_VOTES).handler(authGuard.check()).handler(hVoteLogic::getVoteSelf); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/middlewares/CineAuthGuard.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/middlewares/CineAuthGuard.java new file mode 100644 index 0000000..4ddcdd9 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/routing/middlewares/CineAuthGuard.java @@ -0,0 +1,35 @@ +package net.miarma.api.microservices.huertosdecine.routing.middlewares; + +import java.util.function.Consumer; + +import io.vertx.ext.web.RoutingContext; +import net.miarma.api.backlib.Constants.CineUserRole; +import net.miarma.api.backlib.middlewares.AbstractAuthGuard; +import net.miarma.api.microservices.huertosdecine.entities.ViewerEntity; +import net.miarma.api.microservices.huertosdecine.services.ViewerService; + +public class CineAuthGuard extends AbstractAuthGuard { + private final ViewerService viewerService; + + public CineAuthGuard(ViewerService viewerService) { + this.viewerService = viewerService; + } + + @Override + protected CineUserRole parseRole(String roleStr) { + return CineUserRole.valueOf(roleStr.toUpperCase()); + } + + @Override + protected void getUserEntity(int userId, RoutingContext ctx, Consumer callback) { + viewerService.getById(userId).onComplete(ar -> { + if (ar.succeeded()) callback.accept(ar.result()); + else callback.accept(null); + }); + } + + @Override + protected boolean hasPermission(ViewerEntity user, CineUserRole role) { + return user.getRole() == CineUserRole.ADMIN; + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/MovieService.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/MovieService.java new file mode 100644 index 0000000..8fdc053 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/MovieService.java @@ -0,0 +1,53 @@ +package net.miarma.api.microservices.huertosdecine.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.exceptions.NotFoundException; +import net.miarma.api.backlib.http.QueryParams; +import net.miarma.api.microservices.huertosdecine.dao.MovieDAO; +import net.miarma.api.microservices.huertosdecine.entities.MovieEntity; + +import java.util.List; + +public class MovieService { + private final MovieDAO movieDAO; + + public MovieService(Pool pool) { + this.movieDAO = new MovieDAO(pool); + } + + public Future> getAll(QueryParams params) { + return movieDAO.getAll(params); + } + + public Future getById(Integer id) { + return movieDAO.getById(id).compose(movie -> { + if (movie == null) { + return Future.failedFuture(new NotFoundException("Movie not found in the database")); + } + return Future.succeededFuture(movie); + }); + } + + public Future create(MovieEntity movie) { + return movieDAO.insert(movie); + } + + public Future update(MovieEntity movie) { + return getById(movie.getMovie_id()).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Movie not found in the database")); + } + return movieDAO.update(movie); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Movie not found in the database")); + } + return movieDAO.delete(id); + }); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/ViewerService.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/ViewerService.java new file mode 100644 index 0000000..cf784d0 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/ViewerService.java @@ -0,0 +1,137 @@ +package net.miarma.api.microservices.huertosdecine.services; + +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.exceptions.BadRequestException; +import net.miarma.api.backlib.exceptions.ForbiddenException; +import net.miarma.api.backlib.exceptions.NotFoundException; +import net.miarma.api.backlib.http.QueryParams; +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.services.UserService; +import net.miarma.api.microservices.huertosdecine.dao.UserMetadataDAO; +import net.miarma.api.microservices.huertosdecine.dao.ViewerDAO; +import net.miarma.api.microservices.huertosdecine.entities.UserMetadataEntity; +import net.miarma.api.microservices.huertosdecine.entities.ViewerEntity; +import net.miarma.api.backlib.util.UserNameGenerator; + +import java.security.NoSuchAlgorithmException; +import java.util.List; + +public class ViewerService { + private final UserDAO userDAO; + private final UserMetadataDAO userMetadataDAO; + private final ViewerDAO viewerDAO; + private final UserService userService; + + public ViewerService(Pool pool) { + this.userDAO = new UserDAO(pool); + this.userMetadataDAO = new UserMetadataDAO(pool); + this.viewerDAO = new ViewerDAO(pool); + this.userService = new UserService(pool); + } + + public Future login(String emailOrUsername, String password, boolean keepLoggedIn) { + return userService.login(emailOrUsername, password, keepLoggedIn).compose(json -> { + JsonObject loggedUserJson = json.getJsonObject("loggedUser"); + UserEntity user = Constants.GSON.fromJson(loggedUserJson.encode(), UserEntity.class); + + 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")); + } + + return userMetadataDAO.getById(user.getUser_id()).compose(metadata -> { + if (metadata.getStatus() != Constants.CineUserStatus.ACTIVE) { + return Future.failedFuture(new ForbiddenException("User is not active")); + } + + ViewerEntity viewer = new ViewerEntity(user, metadata); + + return Future.succeededFuture(new JsonObject() + .put("token", json.getString("token")) + .put("loggedUser", new JsonObject(Constants.GSON.toJson(viewer))) + ); + }); + }); + } + + public Future> getAll() { + return viewerDAO.getAll(); + } + + public Future> getAll(QueryParams params) { + return viewerDAO.getAll(params); + } + + public Future getById(Integer id) { + return viewerDAO.getById(id).compose(viewer -> { + if (viewer == null) { + return Future.failedFuture(new NotFoundException("Viewer not found in the database")); + } + return Future.succeededFuture(viewer); + }); + } + + public Future create(ViewerEntity viewer) { + viewer.setPassword(PasswordHasher.hash(viewer.getPassword())); + if (viewer.getEmail() == null || viewer.getEmail().isBlank()) viewer.setEmail(null); + + String baseName = viewer.getDisplay_name().split(" ")[0].toLowerCase(); + String userName; + try { + userName = UserNameGenerator.generateUserName(baseName, viewer.getDisplay_name(), 3); + viewer.setUser_name(userName); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + return userDAO.insert(UserEntity.from(viewer)).compose(user -> { + UserMetadataEntity metadata = UserMetadataEntity.fromViewerEntity(viewer); + metadata.setUser_id(user.getUser_id()); + return userMetadataDAO.upsert(metadata).compose(meta -> + userDAO.update(user).map(updatedUser -> new ViewerEntity(updatedUser, meta))); + }); + } + + public Future createMetadata(UserMetadataEntity userMetadata) { + if (userMetadata.getUser_id() == null) { + return Future.failedFuture(new BadRequestException("User ID is required")); + } + + return userMetadataDAO.upsert(userMetadata).compose(Future::succeededFuture); + } + + public Future update(ViewerEntity viewer) { + return getById(viewer.getUser_id()).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Member in the database")); + } + + if (viewer.getPassword() != null && !viewer.getPassword().isEmpty() && + !viewer.getPassword().equals(existing.getPassword())) { + viewer.setPassword(PasswordHasher.hash(viewer.getPassword())); + } else { + viewer.setPassword(existing.getPassword()); + } + + return userDAO.update(UserEntity.from(viewer)).compose(updatedUser -> userMetadataDAO.update(UserMetadataEntity.fromViewerEntity(viewer)).map(updatedMeta -> new ViewerEntity(updatedUser, updatedMeta))); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(viewer -> + userDAO.delete(id).compose(deletedUser -> + userMetadataDAO.delete(viewer.getUser_id()) + .map(deletedMetadata -> viewer) + ) + ); + } + +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/VoteService.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/VoteService.java new file mode 100644 index 0000000..c5a5e96 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/services/VoteService.java @@ -0,0 +1,57 @@ +package net.miarma.api.microservices.huertosdecine.services; + +import io.vertx.core.Future; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.exceptions.NotFoundException; +import net.miarma.api.backlib.http.QueryParams; +import net.miarma.api.backlib.security.JWTManager; +import net.miarma.api.microservices.huertosdecine.dao.VoteDAO; +import net.miarma.api.microservices.huertosdecine.entities.VoteEntity; + +import java.util.List; + +public class VoteService { + private final VoteDAO voteDAO; + + public VoteService(Pool pool) { + this.voteDAO = new VoteDAO(pool); + } + + public Future> getAll(QueryParams params) { + return voteDAO.getAll(params); + } + + public Future getByUserId(Integer userId) { + return voteDAO.getById(userId); + } + + public Future> getVotesByMovieId(Integer movieId) { + return voteDAO.getVotesByMovieId(movieId).compose(list -> { + if (list.isEmpty()) { + return Future.failedFuture(new NotFoundException("No votes found for the specified movie ID")); + } + return Future.succeededFuture(list); + }); + } + + public Future create(VoteEntity vote) { + return voteDAO.upsert(vote); + } + + public Future delete(Integer userId) { + return getByUserId(userId).compose(existing -> { + if (existing == null) { + return Future.failedFuture(new NotFoundException("Vote not found in the database")); + } + Integer movieId = existing.getMovie_id(); + return voteDAO.delete(movieId); + }); + } + + public Future getVoteSelf(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + return voteDAO.getById(userId); + } + + +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineDataVerticle.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineDataVerticle.java new file mode 100644 index 0000000..f33dfa6 --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineDataVerticle.java @@ -0,0 +1,119 @@ +package net.miarma.api.microservices.huertosdecine.verticles; + +import java.util.stream.Collectors; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.EventBusUtil; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.huertosdecine.entities.VoteEntity; +import net.miarma.api.microservices.huertosdecine.routing.CineDataRouter; +import net.miarma.api.microservices.huertosdecine.services.ViewerService; +import net.miarma.api.microservices.huertosdecine.services.VoteService; + +public class CineDataVerticle extends AbstractVerticle { + private ConfigManager configManager; + private VoteService voteService; + private ViewerService viewerService; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + + voteService = new VoteService(pool); + viewerService = new ViewerService(pool); + + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + CineDataRouter.mount(router, vertx, pool); + + registerLogicVerticleConsumer(); + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("cine.data.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } + + private void registerLogicVerticleConsumer() { + vertx.eventBus().consumer(Constants.CINE_EVENT_BUS, message -> { + JsonObject body = (JsonObject) message.body(); + String action = body.getString("action"); + + switch (action) { + case "login" -> { + String email = body.getString("email", null); + String userName = body.getString("userName", null); + String password = body.getString("password"); + boolean keepLoggedIn = body.getBoolean("keepLoggedIn", false); + + viewerService.login(email != null ? email : userName, password, keepLoggedIn) + .onSuccess(message::reply) + .onFailure(EventBusUtil.fail(message)); + } + + case "getVotesOnMovieByUserId" -> { + Integer movieId = body.getInteger("movie_id"); + voteService.getVotesByMovieId(movieId) + .onSuccess(votes -> { + if (votes.isEmpty()) { + message.reply(new JsonObject().put("message", "No votes found for this movie and viewer")); + } else { + String votesJson = votes.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(votesJson)); + } + }) + .onFailure(EventBusUtil.fail(message)); + } + + + case "getVotes" -> voteService.getVotesByMovieId(body.getInteger("movie_id")) + .onSuccess(votes -> { + String votesJson = votes.stream() + .map(Constants.GSON::toJson) + .collect(Collectors.joining(",", "[", "]")); + message.reply(new JsonArray(votesJson)); + }) + .onFailure(EventBusUtil.fail(message)); + + + case "addVote" -> { + VoteEntity vote = Constants.GSON.fromJson(body.encode(), VoteEntity.class); + voteService.create(vote) + .onSuccess(createdVote -> message.reply(new JsonObject(Constants.GSON.toJson(createdVote)))) + .onFailure(EventBusUtil.fail(message)); + } + + case "deleteVote" -> { + Integer userId = body.getInteger("user_id"); + voteService.delete(userId) + .onSuccess(deletedVote -> message.reply(new JsonObject(Constants.GSON.toJson(deletedVote)))) + .onFailure(EventBusUtil.fail(message)); + } + + case "getVoteSelf" -> { + String token = body.getString("token"); + voteService.getVoteSelf(token) + .onSuccess(vote -> message.reply(new JsonObject(Constants.GSON.toJson(vote)))) + .onFailure(EventBusUtil.fail(message)); + } + + default -> EventBusUtil.fail(message).handle(new IllegalArgumentException("Unknown action: " + action)); + } + }); + } + +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineLogicVerticle.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineLogicVerticle.java new file mode 100644 index 0000000..e6a3c6c --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineLogicVerticle.java @@ -0,0 +1,32 @@ +package net.miarma.api.microservices.huertosdecine.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.huertosdecine.routing.CineLogicRouter; + +public class CineLogicVerticle extends AbstractVerticle { + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + CineLogicRouter.mount(router, vertx, pool); + + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("cine.logic.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } +} diff --git a/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineMainVerticle.java b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineMainVerticle.java new file mode 100644 index 0000000..ec6bfaa --- /dev/null +++ b/microservices/huertosdecine/src/main/java/net/miarma/api/microservices/huertosdecine/verticles/CineMainVerticle.java @@ -0,0 +1,56 @@ +package net.miarma.api.microservices.huertosdecine.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.LogAccumulator; +import net.miarma.api.backlib.util.DeploymentUtil; + +public class CineMainVerticle extends AbstractVerticle { + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + try { + this.configManager = ConfigManager.getInstance(); + deployVerticles(); + startPromise.complete(); + } catch (Exception e) { + Constants.LOGGER.error(DeploymentUtil.failMessage(CineMainVerticle.class, e)); + startPromise.fail(e); + } + } + + private void deployVerticles() { + vertx.deployVerticle(new CineDataVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(CineDataVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("cine.data.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(CineDataVerticle.class, result.cause())); + } + }); + + vertx.deployVerticle(new CineLogicVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(CineLogicVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("cine.logic.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(CineLogicVerticle.class, result.cause())); + } + }); + } +} diff --git a/microservices/miarmacraft/.gitignore b/microservices/miarmacraft/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/microservices/miarmacraft/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/microservices/miarmacraft/pom.xml b/microservices/miarmacraft/pom.xml new file mode 100644 index 0000000..2d50b92 --- /dev/null +++ b/microservices/miarmacraft/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + net.miarma.api + miarmacraft + 1.2.0 + + + 23 + 23 + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + net.miarma.api + backlib + 1.2.0 + + + + + ME-MiarmaCraft + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.api.microservices.miarmacraft.MMCMainVerticle + + + + + + + + + + diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/ModDAO.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/ModDAO.java new file mode 100644 index 0000000..d5828d7 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/ModDAO.java @@ -0,0 +1,131 @@ +package net.miarma.api.microservices.miarmacraft.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.microservices.miarmacraft.entities.ModEntity; + +import java.util.List; +import java.util.Map; + +public class ModDAO implements DataAccessObject { + + private final DatabaseManager db; + + public ModDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ModEntity.class) + .where(Map.of("mod_id", integer.toString())) + .build(); + + db.executeOne(query, ModEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(ModEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + db.execute(query, ModEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + return promise.future(); + } + + @Override + public Future insert(ModEntity mod) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(mod).build(); + + db.executeOne(query, ModEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(ModEntity modEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(modEntity, conflictKeys).build(); + + db.executeOne(query, ModEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future update(ModEntity mod) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(mod).build(); + + db.executeOne(query, ModEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + ModEntity mod = new ModEntity(); + mod.setMod_id(id); + String query = QueryBuilder + .delete(mod) + .build(); + db.executeOne(query, ModEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + return promise.future(); + } + + @Override + public Future exists(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(ModEntity.class) + .where(Map.of("mod_id", id.toString())) + .build(); + + db.executeOne(query, ModEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/PlayerDAO.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/PlayerDAO.java new file mode 100644 index 0000000..d6d15f8 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/PlayerDAO.java @@ -0,0 +1,100 @@ +package net.miarma.api.microservices.miarmacraft.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.microservices.miarmacraft.entities.PlayerEntity; + +import java.util.List; +import java.util.Map; + +public class PlayerDAO implements DataAccessObject { + + private final DatabaseManager db; + + public PlayerDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PlayerEntity.class) + .where(Map.of("user_id", integer.toString())) + .build(); + + db.executeOne(query, PlayerEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + + String query = QueryBuilder + .select(PlayerEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, PlayerEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(PlayerEntity t) { + throw new UnsupportedOperationException("Insert not supported on view-based DAO"); + } + + @Override + public Future upsert(PlayerEntity playerEntity, String... conflictKeys) { + throw new UnsupportedOperationException("Upsert not supported on view-based DAO"); + } + + @Override + public Future update(PlayerEntity t) { + throw new UnsupportedOperationException("Insert not supported on view-based DAO"); + } + + @Override + public Future delete(Integer id) { + throw new UnsupportedOperationException("Insert not supported on view-based DAO"); + } + + @Override + public Future exists(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PlayerEntity.class) + .where(Map.of("user_id", integer.toString())) + .build(); + + db.executeOne(query, PlayerEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/UserMetadataDAO.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/UserMetadataDAO.java new file mode 100644 index 0000000..8455a3b --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/dao/UserMetadataDAO.java @@ -0,0 +1,125 @@ +package net.miarma.api.microservices.miarmacraft.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.microservices.miarmacraft.entities.UserMetadataEntity; + +import java.util.List; +import java.util.Map; + +public class UserMetadataDAO implements DataAccessObject { + + private final DatabaseManager db; + + public UserMetadataDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + @Override + public Future getById(Integer id) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(Map.of("user_id", id.toString())) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, UserMetadataEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(UserMetadataEntity t) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(t).build(); + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + return promise.future(); + } + + @Override + public Future upsert(UserMetadataEntity userMetadataEntity, String... conflictKeys) { + Promise promise = Promise.promise(); + String query = QueryBuilder.upsert(userMetadataEntity, conflictKeys).build(); + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + return promise.future(); + } + + @Override + public Future update(UserMetadataEntity t) { + Promise promise = Promise.promise(); + String query = QueryBuilder.update(t).build(); + db.executeOne(query, UserMetadataEntity.class, + promise::complete, + promise::fail + ); + return promise.future(); + } + + @Override + public Future delete(Integer id) { + Promise promise = Promise.promise(); + UserMetadataEntity entity = new UserMetadataEntity(); + entity.setUser_id(id); + String query = QueryBuilder.delete(entity).build(); + db.executeOne(query, UserMetadataEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + return promise.future(); + } + + @Override + public Future exists(Integer integer) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(UserMetadataEntity.class) + .where(Map.of("user_id", integer.toString())) + .build(); + + db.executeOne(query, UserMetadataEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + + return promise.future(); + } + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/ModEntity.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/ModEntity.java new file mode 100644 index 0000000..8aa1f76 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/ModEntity.java @@ -0,0 +1,65 @@ +package net.miarma.api.microservices.miarmacraft.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.MMCModStatus; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +import java.time.LocalDateTime; + +@Table("miarmacraft_mods") +public class ModEntity extends AbstractEntity { + private Integer mod_id; + private String name; + private String url; + private MMCModStatus status; + private LocalDateTime created_at; + private LocalDateTime updated_at; + + public ModEntity() { + super(); + } + + public ModEntity(Row row) { + super(row); + } + + public Integer getMod_id() { + return mod_id; + } + public void setMod_id(Integer mod_id) { + this.mod_id = mod_id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public MMCModStatus getStatus() { + return status; + } + public void setStatus(MMCModStatus status) { + this.status = status; + } + 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; + } + + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/PlayerEntity.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/PlayerEntity.java new file mode 100644 index 0000000..da75733 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/PlayerEntity.java @@ -0,0 +1,151 @@ +package net.miarma.api.microservices.miarmacraft.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.Constants.MMCUserRole; +import net.miarma.api.backlib.Constants.MMCUserStatus; +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("v_miarmacraft_players") +public class PlayerEntity 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 MMCUserRole role; + private MMCUserStatus status; + private CoreUserGlobalStatus global_status; + private CoreUserRole global_role; + private LocalDateTime created_at; + private LocalDateTime updated_at; + + public PlayerEntity() { + super(); + } + + public PlayerEntity(Row row) { + super(row); + } + + public PlayerEntity(IUser user, UserMetadataEntity userMetadata) { + this.user_id = user.getUser_id(); + this.user_name = user.getUser_name(); + this.email = user.getEmail(); + this.display_name = user.getDisplay_name(); + this.password = user.getPassword(); + this.avatar = user.getAvatar(); + this.role = userMetadata.getRole(); + this.status = userMetadata.getStatus(); + this.global_status = user.getGlobal_status(); + this.global_role = user.getGlobal_role(); + this.created_at = user.getCreated_at(); + this.updated_at = user.getUpdated_at(); + } + + 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 MMCUserRole getRole() { + return role; + } + + public void setRole(MMCUserRole role) { + this.role = role; + } + + public MMCUserStatus getStatus() { + return status; + } + + public void setStatus(MMCUserStatus status) { + this.status = status; + } + + public CoreUserGlobalStatus getGlobal_status() { + return global_status; + } + + public void setGlobal_status(CoreUserGlobalStatus global_status) { + this.global_status = global_status; + } + + public CoreUserRole getGlobal_role() { + return global_role; + } + + public void setGlobal_role(CoreUserRole global_role) { + this.global_role = global_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; + } + + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/UserMetadataEntity.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/UserMetadataEntity.java new file mode 100644 index 0000000..174e6fa --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/entities/UserMetadataEntity.java @@ -0,0 +1,54 @@ +package net.miarma.api.microservices.miarmacraft.entities; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.Constants.MMCUserRole; +import net.miarma.api.backlib.Constants.MMCUserStatus; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +@Table("miarmacraft_user_metadata") +public class UserMetadataEntity extends AbstractEntity { + private Integer user_id; + private MMCUserRole role; + private MMCUserStatus status; + + public UserMetadataEntity() { + super(); + } + + public UserMetadataEntity(Row row) { + super(row); + } + + public Integer getUser_id() { + return user_id; + } + + public void setUser_id(Integer user_id) { + this.user_id = user_id; + } + + public MMCUserRole getRole() { + return role; + } + + public void setRole(MMCUserRole role) { + this.role = role; + } + + public MMCUserStatus getStatus() { + return status; + } + + public void setStatus(MMCUserStatus status) { + this.status = status; + } + + public static UserMetadataEntity fromPlayerEntity(PlayerEntity player) { + UserMetadataEntity userMetadata = new UserMetadataEntity(); + userMetadata.setUser_id(player.getUser_id()); + userMetadata.setRole(player.getRole()); + userMetadata.setStatus(player.getStatus()); + return userMetadata; + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/ModDataHandler.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/ModDataHandler.java new file mode 100644 index 0000000..59ec031 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/ModDataHandler.java @@ -0,0 +1,115 @@ +package net.miarma.api.microservices.miarmacraft.handlers; + +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.microservices.miarmacraft.entities.ModEntity; +import net.miarma.api.microservices.miarmacraft.services.ModService; +import net.miarma.api.backlib.util.JsonUtil; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ModDataHandler { + final ModService modService; + + public ModDataHandler(Pool pool) { + this.modService = new ModService(pool); + } + + public void getAll(RoutingContext ctx) { + modService.getAll() + .onSuccess(mods -> JsonUtil.sendJson(ctx, ApiStatus.OK, mods)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer modId = Integer.parseInt(ctx.pathParam("mod_id")); + modService.getById(modId) + .onSuccess(mod -> JsonUtil.sendJson(ctx, ApiStatus.OK, mod)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + try { + String jsonData = ctx.request().getFormAttribute("data"); + if (jsonData == null) { + JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Falta el campo 'data' con los datos del mod"); + return; + } + + ModEntity mod = Constants.GSON.fromJson(jsonData, ModEntity.class); + if (mod == null) { + JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "JSON del mod inválido"); + return; + } + + var fileUploadOpt = ctx.fileUploads().stream().findFirst(); + if (fileUploadOpt.isEmpty()) { + JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Falta el archivo .jar del mod"); + return; + } + + var fileUpload = fileUploadOpt.get(); + String originalFileName = fileUpload.fileName(); + if (!originalFileName.endsWith(".jar")) { + JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Solo se permiten archivos .jar"); + return; + } + + String sanitizedFileName = originalFileName.replaceAll("[^a-zA-Z0-9._-]", "_"); + String modsDir = ConfigManager.getInstance().getModsDir(); + Path tempPath = Paths.get(fileUpload.uploadedFileName()); + Path modsDirPath = Paths.get(modsDir); + Path targetPath = modsDirPath.resolve(sanitizedFileName); + + Files.createDirectories(modsDirPath); + + Files.move(tempPath, targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + mod.setUrl("/files/miarmacraft/mods/" + sanitizedFileName); + + modService.create(mod) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> { + Constants.LOGGER.error(err.getMessage()); + JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, err.getMessage()); + }); + + } catch (Exception e) { + Constants.LOGGER.error("Error al procesar la petición de creación de mod: {}", e.getMessage(), e); + JsonUtil.sendJson(ctx, ApiStatus.BAD_REQUEST, null, "Error procesando la petición: " + e.getMessage()); + } + } + + public void update(RoutingContext ctx) { + ModEntity mod = Constants.GSON.fromJson(ctx.body().asString(), ModEntity.class); + modService.update(mod) + .onSuccess(_ -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer modId = Integer.parseInt(ctx.pathParam("mod_id")); + + modService.getById(modId).onSuccess(mod -> { + String modsDir = ConfigManager.getInstance().getModsDir(); + String filename = Paths.get(mod.getUrl()).getFileName().toString(); + Path fullPath = Paths.get(modsDir, filename); + + ctx.vertx().fileSystem().delete(fullPath.toString(), fileRes -> { + if (fileRes.failed()) { + Constants.LOGGER.warn("No se pudo eliminar el archivo del mod: {}", fullPath, fileRes.cause()); + } + + modService.delete(modId) + .onSuccess(_ -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + }); + }).onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/ModLogicHandler.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/ModLogicHandler.java new file mode 100644 index 0000000..3d398c8 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/ModLogicHandler.java @@ -0,0 +1,5 @@ +package net.miarma.api.microservices.miarmacraft.handlers; + +public class ModLogicHandler { + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/PlayerDataHandler.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/PlayerDataHandler.java new file mode 100644 index 0000000..6754c7e --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/PlayerDataHandler.java @@ -0,0 +1,51 @@ +package net.miarma.api.microservices.miarmacraft.handlers; + +import io.vertx.ext.web.RoutingContext; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.microservices.miarmacraft.entities.PlayerEntity; +import net.miarma.api.microservices.miarmacraft.services.PlayerService; +import net.miarma.api.backlib.util.JsonUtil; + +public class PlayerDataHandler { + private final PlayerService playerService; + + public PlayerDataHandler(Pool pool) { + this.playerService = new PlayerService(pool); + } + + public void getAll(RoutingContext ctx) { + playerService.getAll() + .onSuccess(players -> JsonUtil.sendJson(ctx, ApiStatus.OK, players)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.pathParam("player_id")); + playerService.getById(playerId) + .onSuccess(player -> JsonUtil.sendJson(ctx, ApiStatus.OK, player)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + PlayerEntity player = Constants.GSON.fromJson(ctx.body().asString(), PlayerEntity.class); + playerService.create(player) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + PlayerEntity player = Constants.GSON.fromJson(ctx.body().asString(), PlayerEntity.class); + playerService.update(player) + .onSuccess(_ -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void delete(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.pathParam("player_id")); + playerService.delete(playerId) + .onSuccess(_ -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/PlayerLogicHandler.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/PlayerLogicHandler.java new file mode 100644 index 0000000..50d66f3 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/handlers/PlayerLogicHandler.java @@ -0,0 +1,116 @@ +package net.miarma.api.microservices.miarmacraft.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class PlayerLogicHandler { + private final Vertx vertx; + + public PlayerLogicHandler(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.MMC_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(), "The player is inactive or banned"); + } + }); + } + + public void getStatus(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject request = new JsonObject().put("action", "getStatus").put("playerId", playerId); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void updateStatus(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject body = ctx.body().asJsonObject(); + JsonObject request = new JsonObject().put("action", "updateStatus").put("playerId", playerId) + .put("status", body.getString("status")); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void getRole(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject request = new JsonObject().put("action", "getRole").put("playerId", playerId); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void updateRole(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject body = ctx.body().asJsonObject(); + JsonObject request = new JsonObject().put("action", "updateRole").put("playerId", playerId) + .put("role", body.getString("role")); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void getAvatar(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject request = new JsonObject().put("action", "getAvatar").put("playerId", playerId); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void updateAvatar(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject body = ctx.body().asJsonObject(); + JsonObject request = new JsonObject().put("action", "updateAvatar").put("playerId", playerId) + .put("avatar", body.getString("avatar")); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void getInfo(RoutingContext ctx) { + String token = ctx.request().getHeader("Authorization").substring("Bearer ".length()); + JsonObject request = new JsonObject().put("action", "getInfo").put("token", token); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + + public void playerExists(RoutingContext ctx) { + Integer playerId = Integer.parseInt(ctx.request().getParam("player_id")); + JsonObject request = new JsonObject().put("action", "playerExists").put("playerId", playerId); + vertx.eventBus().request(Constants.MMC_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Player not found"); + }); + } + +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCDataRouter.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCDataRouter.java new file mode 100644 index 0000000..af8fe76 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCDataRouter.java @@ -0,0 +1,34 @@ +package net.miarma.api.microservices.miarmacraft.routing; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants.MMCUserRole; +import net.miarma.api.microservices.miarmacraft.handlers.ModDataHandler; +import net.miarma.api.microservices.miarmacraft.handlers.PlayerDataHandler; +import net.miarma.api.microservices.miarmacraft.routing.middlewares.MMCAuthGuard; +import net.miarma.api.microservices.miarmacraft.services.PlayerService; + +public class MMCDataRouter { + public static void mount(Router router, Vertx vertx, Pool pool) { + ModDataHandler hModData = new ModDataHandler(pool); + PlayerDataHandler hPlayerData = new PlayerDataHandler(pool); + PlayerService playerService = new PlayerService(pool); + MMCAuthGuard authGuard = new MMCAuthGuard(playerService); + + router.route().handler(BodyHandler.create()); + + router.get(MMCEndpoints.MODS).handler(authGuard.check()).handler(hModData::getAll); + router.get(MMCEndpoints.MOD).handler(authGuard.check()).handler(hModData::getById); + router.post(MMCEndpoints.MODS).handler(BodyHandler.create().setBodyLimit(100 * 1024 * 1024)).handler(authGuard.check()).handler(hModData::create); + router.put(MMCEndpoints.MOD).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hModData::update); + router.delete(MMCEndpoints.MOD).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hModData::delete); + + router.get(MMCEndpoints.PLAYERS).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hPlayerData::getAll); + router.post(MMCEndpoints.PLAYERS).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hPlayerData::create); + router.put(MMCEndpoints.PLAYER).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hPlayerData::update); + router.delete(MMCEndpoints.PLAYER).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hPlayerData::delete); + router.get(MMCEndpoints.PLAYER).handler(authGuard.check(MMCUserRole.ADMIN)).handler(hPlayerData::getById); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCEndpoints.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCEndpoints.java new file mode 100644 index 0000000..7cd93ac --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCEndpoints.java @@ -0,0 +1,19 @@ +package net.miarma.api.microservices.miarmacraft.routing; + +import net.miarma.api.backlib.Constants; + +public class MMCEndpoints { + public static final String LOGIN = Constants.MMC_PREFIX + "/login"; // POST + + public static final String MODS = Constants.MMC_PREFIX + "/mods"; // GET, POST, PUT, DELETE + public static final String MOD = Constants.MMC_PREFIX + "/mods/:mod_id"; // GET, PUT, DELETE + public static final String MOD_STATUS = Constants.MMC_PREFIX + "/mods/:mod_id/status"; // GET, PUT + + public static final String PLAYERS = Constants.MMC_PREFIX + "/players"; // GET, POST, PUT, DELETE + public static final String PLAYER = Constants.MMC_PREFIX + "/players/:player_id"; // GET, PUT, DELETE + public static final String PLAYER_STATUS = Constants.MMC_PREFIX + "/players/:player_id/status"; // GET, PUT + public static final String PLAYER_ROLE = Constants.MMC_PREFIX + "/players/:player_id/role"; // GET, PUT + public static final String PLAYER_EXISTS = Constants.MMC_PREFIX + "/players/:player_id/exists"; // GET + public static final String PLAYER_AVATAR = Constants.MMC_PREFIX + "/players/:player_id/avatar"; // GET, PUT + public static final String PLAYER_INFO = Constants.MMC_PREFIX + "/players/me"; // GET +} \ No newline at end of file diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCLogicRouter.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCLogicRouter.java new file mode 100644 index 0000000..edda36a --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/MMCLogicRouter.java @@ -0,0 +1,29 @@ +package net.miarma.api.microservices.miarmacraft.routing; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.microservices.miarmacraft.handlers.PlayerLogicHandler; +import net.miarma.api.microservices.miarmacraft.routing.middlewares.MMCAuthGuard; +import net.miarma.api.microservices.miarmacraft.services.PlayerService; + +public class MMCLogicRouter { + public static void mount(Router router, Vertx vertx, Pool pool) { + PlayerLogicHandler hPlayerLogic = new PlayerLogicHandler(vertx); + PlayerService playerService = new PlayerService(pool); + MMCAuthGuard authGuard = new MMCAuthGuard(playerService); + + router.route().handler(BodyHandler.create()); + + router.post(MMCEndpoints.LOGIN).handler(hPlayerLogic::login); + router.get(MMCEndpoints.PLAYER_STATUS).handler(authGuard.check()).handler(hPlayerLogic::getStatus); + router.put(MMCEndpoints.PLAYER_STATUS).handler(authGuard.check()).handler(hPlayerLogic::updateStatus); + router.get(MMCEndpoints.PLAYER_ROLE).handler(authGuard.check()).handler(hPlayerLogic::getRole); + router.put(MMCEndpoints.PLAYER_ROLE).handler(authGuard.check()).handler(hPlayerLogic::updateRole); + router.get(MMCEndpoints.PLAYER_AVATAR).handler(authGuard.check()).handler(hPlayerLogic::getAvatar); + router.put(MMCEndpoints.PLAYER_AVATAR).handler(authGuard.check()).handler(hPlayerLogic::updateAvatar); + router.get(MMCEndpoints.PLAYER_INFO).handler(authGuard.check()).handler(hPlayerLogic::getInfo); + router.get(MMCEndpoints.PLAYER_EXISTS).handler(authGuard.check()).handler(hPlayerLogic::playerExists); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/middlewares/MMCAuthGuard.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/middlewares/MMCAuthGuard.java new file mode 100644 index 0000000..09b52b3 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/routing/middlewares/MMCAuthGuard.java @@ -0,0 +1,37 @@ +package net.miarma.api.microservices.miarmacraft.routing.middlewares; + +import java.util.function.Consumer; + +import io.vertx.ext.web.RoutingContext; +import net.miarma.api.backlib.Constants.MMCUserRole; +import net.miarma.api.backlib.middlewares.AbstractAuthGuard; +import net.miarma.api.microservices.miarmacraft.entities.PlayerEntity; +import net.miarma.api.microservices.miarmacraft.services.PlayerService; + +public class MMCAuthGuard extends AbstractAuthGuard { + private final PlayerService playerService; + + public MMCAuthGuard(PlayerService playerService) { + this.playerService = playerService; + } + + @Override + protected MMCUserRole parseRole(String roleStr) { + return MMCUserRole.valueOf(roleStr.toUpperCase()); + } + + @Override + protected void getUserEntity(int userId, RoutingContext ctx, Consumer callback) { + playerService.getById(userId).onComplete(ar -> { + if (ar.succeeded()) callback.accept(ar.result()); + else callback.accept(null); + }); + } + + @Override + protected boolean hasPermission(PlayerEntity user, MMCUserRole role) { + return user.getRole() == MMCUserRole.ADMIN; + } + +} + diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/services/ModService.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/services/ModService.java new file mode 100644 index 0000000..a1e7436 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/services/ModService.java @@ -0,0 +1,84 @@ +package net.miarma.api.microservices.miarmacraft.services; + +import com.eduardomcb.discord.webhook.WebhookClient; +import com.eduardomcb.discord.webhook.WebhookManager; +import com.eduardomcb.discord.webhook.models.Message; +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.exceptions.NotFoundException; +import net.miarma.api.backlib.http.QueryParams; +import net.miarma.api.microservices.miarmacraft.dao.ModDAO; +import net.miarma.api.microservices.miarmacraft.entities.ModEntity; + +import java.util.List; + +public class ModService { + private final ModDAO modDAO; + private final ConfigManager configManager = ConfigManager.getInstance(); + + public ModService(Pool pool) { + this.modDAO = new ModDAO(pool); + } + + private void sendWebhookMessage(Message message) { + WebhookManager webhookManager = new WebhookManager() + .setChannelUrl(configManager.getStringProperty("discord.webhook")) + .setMessage(message); + webhookManager.setListener(new WebhookClient.Callback() { + @Override + public void onSuccess(String response) { + Constants.LOGGER.info("Webhook sent successfully"); + } + + @Override + public void onFailure(int statusCode, String errorMessage) { + Constants.LOGGER.error("Failed to send webhook: {}", errorMessage); + } + }); + webhookManager.exec(); + } + + public Future> getAll() { + return modDAO.getAll(); + } + + public Future> getAll(QueryParams params) { + return modDAO.getAll(params); + } + + public Future getById(Integer id) { + return modDAO.getById(id).compose(mod -> { + if (mod == null) { + return Future.failedFuture(new NotFoundException("Mod with id " + id)); + } + return Future.succeededFuture(mod); + }); + } + + public Future update(ModEntity mod) { + return modDAO.update(mod); + } + + public Future create(ModEntity mod) { + return modDAO.insert(mod).compose(createdMod -> { + Message message = new Message() + .setContent("Se ha añadido el mod **" + createdMod.getName() + "** a la lista @everyone"); + sendWebhookMessage(message); + return Future.succeededFuture(createdMod); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(mod -> { + if (mod == null) { + return Future.failedFuture(new NotFoundException("Mod with id " + id)); + } + Message message = new Message() + .setContent("Se ha eliminado el mod **" + mod.getName() + "** de la lista @everyone"); + sendWebhookMessage(message); + return modDAO.delete(id); + }); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/services/PlayerService.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/services/PlayerService.java new file mode 100644 index 0000000..2d1a008 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/services/PlayerService.java @@ -0,0 +1,199 @@ +package net.miarma.api.microservices.miarmacraft.services; + +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.MMCUserRole; +import net.miarma.api.backlib.Constants.MMCUserStatus; +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.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.services.UserService; +import net.miarma.api.microservices.miarmacraft.dao.PlayerDAO; +import net.miarma.api.microservices.miarmacraft.dao.UserMetadataDAO; +import net.miarma.api.microservices.miarmacraft.entities.PlayerEntity; +import net.miarma.api.microservices.miarmacraft.entities.UserMetadataEntity; + +import java.util.List; + +public class PlayerService { + private final UserDAO userDAO; + private final PlayerDAO playerDAO; + private final UserMetadataDAO userMetadataDAO; + private final UserService userService; + + public PlayerService(Pool pool) { + this.userDAO = new UserDAO(pool); + this.playerDAO = new PlayerDAO(pool); + this.userMetadataDAO = new UserMetadataDAO(pool); + this.userService = new UserService(pool); + } + + public Future login(String emailOrUserName, String password, boolean keepLoggedIn) { + return userService.login(emailOrUserName, password, keepLoggedIn).compose(json -> { + JsonObject loggedUserJson = json.getJsonObject("loggedUser"); + UserEntity user = Constants.GSON.fromJson(loggedUserJson.encode(), UserEntity.class); + + 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")); + } + + return userMetadataDAO.getById(user.getUser_id()).compose(metadata -> { + if (metadata == null) { + return Future.failedFuture(new NotFoundException("User metadata not found")); + } + + if (metadata.getStatus() != MMCUserStatus.ACTIVE) { + return Future.failedFuture(new ForbiddenException("User is not active")); + } + + PlayerEntity player = new PlayerEntity(user, metadata); + + return Future.succeededFuture(new JsonObject() + .put("token", json.getString("token")) + .put("loggedUser", new JsonObject(Constants.GSON.toJson(player))) + ); + + }); + }); + } + + public Future> getAll() { + return playerDAO.getAll(); + } + + public Future> getAll(QueryParams queryParams) { + return playerDAO.getAll(queryParams); + } + + public Future getById(Integer id) { + return playerDAO.getById(id).compose(player -> { + if (player == null) { + return Future.failedFuture(new NotFoundException("Player not found")); + } + + return Future.succeededFuture(player); + }); + } + + public Future getStatus(Integer id) { + return getById(id).compose(player -> { + if (player == null) { + return Future.failedFuture(new NotFoundException("Player not found")); + } + + return Future.succeededFuture(player.getStatus()); + }); + } + + public Future getRole(Integer id) { + return getById(id).compose(player -> { + if (player == null) { + return Future.failedFuture(new NotFoundException("Player not found")); + } + + return Future.succeededFuture(player.getRole()); + }); + } + + public Future getAvatar(Integer id) { + return getById(id).compose(player -> { + if (player == null) { + return Future.failedFuture(new NotFoundException("Player not found")); + } + + return Future.succeededFuture(player.getAvatar()); + }); + } + + public Future updateStatus(Integer id, MMCUserStatus status) { + PlayerEntity player = new PlayerEntity(); + player.setUser_id(id); + player.setStatus(status); + return update(player); + } + + public Future updateRole(Integer id, MMCUserRole role) { + PlayerEntity player = new PlayerEntity(); + player.setUser_id(id); + player.setRole(role); + return update(player); + } + + public Future updateAvatar(Integer id, String avatar) { + PlayerEntity player = new PlayerEntity(); + player.setUser_id(id); + player.setAvatar(avatar); + return update(player); + } + + public Future create(PlayerEntity player) { + return getById(player.getUser_id()).compose(existingPlayer -> { + if (existingPlayer != null) { + return Future.failedFuture(new AlreadyExistsException("Player already exists")); + } + + player.setPassword(PasswordHasher.hash(player.getPassword())); + + return userDAO.insert(UserEntity.from(player)).compose(user -> { + UserMetadataEntity metadata = UserMetadataEntity.fromPlayerEntity(player); + metadata.setUser_id(user.getUser_id()); + return userMetadataDAO.insert(metadata).map(_ -> player); + }); + }); + } + + public Future update(PlayerEntity player) { + return getById(player.getUser_id()).compose(existingPlayer -> { + if (existingPlayer == null) { + return Future.failedFuture(new NotFoundException("Player does not exist")); + } + + return userDAO.update(UserEntity.from(player)).compose(user -> { + UserMetadataEntity userMetadata = UserMetadataEntity.fromPlayerEntity(player); + return userMetadataDAO.update(userMetadata).map(_ -> player); + }); + }); + } + + public Future delete(Integer id) { + return getById(id).compose(existingPlayer -> { + if (existingPlayer == null) { + return Future.failedFuture(new NotFoundException("Player does not exist")); + } + + return userDAO.delete(id).compose(_ -> userMetadataDAO.delete(id).map(_ -> existingPlayer)); + }); + } + + public Future playerExists(Integer id) { + return playerDAO.exists(id).compose(exists -> { + if (!exists) { + return Future.failedFuture(new NotFoundException("Player does not exist")); + } + return Future.succeededFuture(true); + }); + } + + public Future getInfo(String token) { + Integer userId = JWTManager.getInstance().getUserId(token); + return getById(userId).compose(player -> { + if (player == null) { + return Future.failedFuture(new NotFoundException("Player not found")); + } + + return Future.succeededFuture(player); + }); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCDataVerticle.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCDataVerticle.java new file mode 100644 index 0000000..a496573 --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCDataVerticle.java @@ -0,0 +1,106 @@ +package net.miarma.api.microservices.miarmacraft.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.Constants.MMCUserRole; +import net.miarma.api.backlib.Constants.MMCUserStatus; +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.miarmacraft.routing.MMCDataRouter; +import net.miarma.api.microservices.miarmacraft.services.PlayerService; + +public class MMCDataVerticle extends AbstractVerticle { + private ConfigManager configManager; + private PlayerService playerService; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + + playerService = new PlayerService(pool); + + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + MMCDataRouter.mount(router, vertx, pool); + + registerLogicVerticleConsumer(); + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("mmc.data.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } + + private void registerLogicVerticleConsumer() { + vertx.eventBus().consumer(Constants.MMC_EVENT_BUS, message -> { + JsonObject body = (JsonObject) message.body(); + String action = body.getString("action"); + + switch(action) { + case "login" -> { + String email = body.getString("email", null); + String userName = body.getString("userName", null); + String password = body.getString("password"); + boolean keepLoggedIn = body.getBoolean("keepLoggedIn", false); + + playerService.login(email != null ? email : userName, password, keepLoggedIn) + .onSuccess(message::reply) + .onFailure(EventBusUtil.fail(message)); + } + + case "getStatus" -> playerService.getStatus(body.getInteger("playerId")) + .onSuccess(player -> message.reply(new JsonObject(Constants.GSON.toJson(player)))) + .onFailure(EventBusUtil.fail(message)); + + case "getRole" -> playerService.getRole(body.getInteger("playerId")) + .onSuccess(role -> message.reply(new JsonObject(Constants.GSON.toJson(role)))) + .onFailure(EventBusUtil.fail(message)); + + case "getAvatar" -> playerService.getAvatar(body.getInteger("playerId")) + .onSuccess(avatar -> message.reply(new JsonObject(Constants.GSON.toJson(avatar)))) + .onFailure(EventBusUtil.fail(message)); + + case "updateStatus" -> { + MMCUserStatus status = MMCUserStatus.fromInt(body.getInteger("status")); + playerService.updateStatus(body.getInteger("playerId"), status) + .onSuccess(result -> message.reply(new JsonObject(Constants.GSON.toJson(result)))) + .onFailure(EventBusUtil.fail(message)); + } + + case "updateRole" -> { + MMCUserRole role = MMCUserRole.fromInt(body.getInteger("role")); + playerService.updateRole(body.getInteger("playerId"), role) + .onSuccess(result -> message.reply(new JsonObject(Constants.GSON.toJson(result)))) + .onFailure(EventBusUtil.fail(message)); + } + + case "updateAvatar" -> { + String avatar = body.getString("avatar"); + playerService.updateAvatar(body.getInteger("playerId"), avatar) + .onSuccess(result -> message.reply(new JsonObject(Constants.GSON.toJson(result)))) + .onFailure(EventBusUtil.fail(message)); + } + + case "playerExists" -> playerService.playerExists(body.getInteger("playerId")) + .onSuccess(exists -> message.reply(new JsonObject(Constants.GSON.toJson(exists)))) + .onFailure(EventBusUtil.fail(message)); + + case "getInfo" -> playerService.getInfo(body.getString("token")) + .onSuccess(player -> message.reply(new JsonObject(Constants.GSON.toJson(player)))) + .onFailure(EventBusUtil.fail(message)); + + default -> EventBusUtil.fail(message).handle(new IllegalArgumentException("Unknown action: " + action)); + } + }); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCLogicVerticle.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCLogicVerticle.java new file mode 100644 index 0000000..081597b --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCLogicVerticle.java @@ -0,0 +1,31 @@ +package net.miarma.api.microservices.miarmacraft.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.backlib.util.RouterUtil; +import net.miarma.api.microservices.miarmacraft.routing.MMCLogicRouter; + +public class MMCLogicVerticle extends AbstractVerticle { + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + MMCLogicRouter.mount(router, vertx, pool); + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("mmc.logic.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } +} diff --git a/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCMainVerticle.java b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCMainVerticle.java new file mode 100644 index 0000000..3ba3b8c --- /dev/null +++ b/microservices/miarmacraft/src/main/java/net/miarma/api/microservices/miarmacraft/verticles/MMCMainVerticle.java @@ -0,0 +1,57 @@ +package net.miarma.api.microservices.miarmacraft.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.LogAccumulator; +import net.miarma.api.backlib.util.DeploymentUtil; + +public class MMCMainVerticle extends AbstractVerticle { + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + try { + this.configManager = ConfigManager.getInstance(); + deployVerticles(); + startPromise.complete(); + } catch (Exception e) { + Constants.LOGGER.error(DeploymentUtil.failMessage(MMCMainVerticle.class, e)); + startPromise.fail(e); + } + } + + private void deployVerticles() { + vertx.deployVerticle(new MMCDataVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(MMCDataVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("mmc.data.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(MMCDataVerticle.class, result.cause())); + } + }); + + vertx.deployVerticle(new MMCLogicVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(MMCLogicVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("mmc.logic.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(MMCLogicVerticle.class, result.cause())); + } + }); + } + +} diff --git a/microservices/mpaste/.gitignore b/microservices/mpaste/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/microservices/mpaste/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/microservices/mpaste/pom.xml b/microservices/mpaste/pom.xml new file mode 100644 index 0000000..a97b982 --- /dev/null +++ b/microservices/mpaste/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + net.miarma.api + mpaste + 1.2.0 + + + 23 + 23 + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + net.miarma.api + backlib + 1.2.0 + + + + + ME-MPaste + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + net.miarma.api.microservices.mpaste.MPasteMainVerticle + + + + + + + + + + diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/dao/PasteDAO.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/dao/PasteDAO.java new file mode 100644 index 0000000..699237f --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/dao/PasteDAO.java @@ -0,0 +1,124 @@ +package net.miarma.api.microservices.mpaste.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.microservices.mpaste.entities.PasteEntity; + +import java.util.List; +import java.util.Map; + +public class PasteDAO implements DataAccessObject { + + private final DatabaseManager db; + + public PasteDAO(Pool pool) { + this.db = DatabaseManager.getInstance(pool); + } + + @Override + public Future> getAll() { + return getAll(new QueryParams(Map.of(), new QueryFilters())); + } + + public Future> getAll(QueryParams params) { + Promise> promise = Promise.promise(); + String query = QueryBuilder + .select(PasteEntity.class) + .where(params.getFilters()) + .orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder()) + .limit(params.getQueryFilters().getLimit()) + .offset(params.getQueryFilters().getOffset()) + .build(); + + db.execute(query, PasteEntity.class, + list -> promise.complete(list.isEmpty() ? List.of() : list), + promise::fail + ); + return promise.future(); + } + + @Override + public Future getById(Long id) { + throw new UnsupportedOperationException("You cannot get this type by its ID"); + } + + public Future getByKey(String key) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PasteEntity.class) + .where(Map.of("paste_key", key)) + .build(); + + db.executeOne(query, PasteEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future insert(PasteEntity paste) { + Promise promise = Promise.promise(); + String query = QueryBuilder.insert(paste).build(); + + db.executeOne(query, PasteEntity.class, + promise::complete, + promise::fail + ); + + return promise.future(); + } + + @Override + public Future upsert(PasteEntity paste, String... conflictKeys) { + throw new UnsupportedOperationException("Upsert not supported on this type"); + } + + @Override + public Future update(PasteEntity paste) { + throw new UnsupportedOperationException("Update not supported on this type"); + } + + @Override + public Future delete(Long id) { + Promise promise = Promise.promise(); + PasteEntity paste = new PasteEntity(); + paste.setPaste_id(id); + String query = QueryBuilder + .delete(paste) + .build(); + + db.executeOne(query, PasteEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + return promise.future(); + } + + @Override + public Future exists(Long id) { + throw new UnsupportedOperationException("You cannot check existance on this type by its ID"); + } + + public Future existsByKey(String key) { + Promise promise = Promise.promise(); + String query = QueryBuilder + .select(PasteEntity.class) + .where(Map.of("paste_key", key)) + .build(); + + db.executeOne(query, PasteEntity.class, + result -> promise.complete(result != null), + promise::fail + ); + return promise.future(); + } + +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/entities/PasteEntity.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/entities/PasteEntity.java new file mode 100644 index 0000000..8ad7961 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/entities/PasteEntity.java @@ -0,0 +1,129 @@ +package net.miarma.api.microservices.mpaste.entities; + +import java.time.LocalDateTime; + +import io.vertx.sqlclient.Row; +import net.miarma.api.backlib.annotations.APIDontReturn; +import net.miarma.api.backlib.annotations.Table; +import net.miarma.api.backlib.db.AbstractEntity; + +@Table("mpaste_pastes") +public class PasteEntity extends AbstractEntity { + private Long paste_id; + private String paste_key; + private String title; + private String content; + private String syntax; + private LocalDateTime created_at; + private LocalDateTime expires_at; + private Integer views; + private Boolean burn_after; + private Boolean is_private; + @APIDontReturn + private String password; + private Integer owner_id; + + public PasteEntity() { + super(); + } + + public PasteEntity(Row row) { + super(row); + } + + public Long getPaste_id() { + return paste_id; + } + + public void setPaste_id(Long paste_id) { + this.paste_id = paste_id; + } + + public String getPaste_key() { + return paste_key; + } + + public void setPaste_key(String paste_key) { + this.paste_key = paste_key; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getSyntax() { + return syntax; + } + + public void setSyntax(String syntax) { + this.syntax = syntax; + } + + public LocalDateTime getCreated_at() { + return created_at; + } + + public void setCreated_at(LocalDateTime created_at) { + this.created_at = created_at; + } + + public LocalDateTime getExpires_at() { + return expires_at; + } + + public void setExpires_at(LocalDateTime expires_at) { + this.expires_at = expires_at; + } + + public Integer getViews() { + return views; + } + + public void setViews(Integer views) { + this.views = views; + } + + public Boolean getBurn_after() { + return burn_after; + } + + public void setBurn_after(Boolean burn_after) { + this.burn_after = burn_after; + } + + public Boolean getIs_private() { + return is_private; + } + + public void setIs_private(Boolean is_private) { + this.is_private = is_private; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getOwner_id() { + return owner_id; + } + + public void setOwner_id(Integer owner_id) { + this.owner_id = owner_id; + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/handlers/PasteDataHandler.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/handlers/PasteDataHandler.java new file mode 100644 index 0000000..a1a0f05 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/handlers/PasteDataHandler.java @@ -0,0 +1,57 @@ +package net.miarma.api.microservices.mpaste.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.microservices.mpaste.entities.PasteEntity; +import net.miarma.api.microservices.mpaste.services.PasteService; +import net.miarma.api.backlib.util.JsonUtil; + +@SuppressWarnings("unused") +public class PasteDataHandler { + + private final PasteService pasteService; + + public PasteDataHandler(Pool pool) { + this.pasteService = new PasteService(pool); + } + + public void getAll(RoutingContext ctx) { + QueryParams params = QueryParams.from(ctx); + + pasteService.getAll(params) + .onSuccess(pastes -> JsonUtil.sendJson(ctx, ApiStatus.OK, pastes)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void getById(RoutingContext ctx) { + Long pasteId = Long.parseLong(ctx.pathParam("paste_id")); + + pasteService.getById(pasteId) + .onSuccess(paste -> JsonUtil.sendJson(ctx, ApiStatus.OK, paste)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void create(RoutingContext ctx) { + PasteEntity paste = Constants.GSON.fromJson(ctx.body().asString(), PasteEntity.class); + + pasteService.create(paste) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } + + public void update(RoutingContext ctx) { + PasteEntity paste = Constants.GSON.fromJson(ctx.body().asString(), PasteEntity.class); + pasteService.update(paste); + } + + public void delete(RoutingContext ctx) { + Long pasteId = Long.parseLong(ctx.pathParam("paste_id")); + + pasteService.delete(pasteId) + .onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null)) + .onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage())); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/handlers/PasteLogicHandler.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/handlers/PasteLogicHandler.java new file mode 100644 index 0000000..37a72d9 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/handlers/PasteLogicHandler.java @@ -0,0 +1,31 @@ +package net.miarma.api.microservices.mpaste.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.util.EventBusUtil; +import net.miarma.api.backlib.util.JsonUtil; + +public class PasteLogicHandler { + private final Vertx vertx; + + public PasteLogicHandler(Vertx vertx) { + this.vertx = vertx; + } + + public void getByKey(RoutingContext ctx) { + String key = ctx.request().getParam("paste_key"); + String password = ctx.request().getHeader("X-Paste-Password"); + JsonObject request = new JsonObject() + .put("action", "getByKey") + .put("key", key) + .put("password", password); + + vertx.eventBus().request(Constants.MPASTE_EVENT_BUS, request, ar -> { + if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body()); + else EventBusUtil.handleReplyError(ctx, ar.cause(), "Paste not found"); + }); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteDataRouter.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteDataRouter.java new file mode 100644 index 0000000..9518d9f --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteDataRouter.java @@ -0,0 +1,37 @@ +package net.miarma.api.microservices.mpaste.routing; + +import io.vertx.core.Vertx; +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.http.ApiResponse; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.security.SusPather; +import net.miarma.api.microservices.mpaste.handlers.PasteDataHandler; +import net.miarma.api.backlib.util.RateLimiter; + +public class MPasteDataRouter { + private static RateLimiter limiter = new RateLimiter(); + + public static void mount(Router router, Vertx vertx, Pool pool) { + PasteDataHandler hPasteData = new PasteDataHandler(pool); + + router.route().handler(BodyHandler.create()); + + router.get(MPasteEndpoints.PASTES).handler(hPasteData::getAll); + router.post(MPasteEndpoints.PASTES).handler(ctx -> { + String ip = ctx.request().remoteAddress().host(); + if (!limiter.allow(ip)) { + ApiResponse response = new ApiResponse<>(ApiStatus.TOO_MANY_REQUESTS, "Too many requests", null); + JsonObject jsonResponse = new JsonObject().put("status", response.getStatus()).put("message", + response.getMessage()); + ctx.response().setStatusCode(response.getStatus()).putHeader("Content-Type", "application/json") + .end(jsonResponse.encode()); + } + hPasteData.create(ctx); + }); + router.get(MPasteEndpoints.PASTE).handler(hPasteData::getById); + router.delete(MPasteEndpoints.PASTE).handler(hPasteData::delete); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteEndpoints.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteEndpoints.java new file mode 100644 index 0000000..1b6949b --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteEndpoints.java @@ -0,0 +1,9 @@ +package net.miarma.api.microservices.mpaste.routing; + +import net.miarma.api.backlib.Constants; + +public class MPasteEndpoints { + public static final String PASTES = Constants.MPASTE_PREFIX + "/pastes"; // GET, POST + public static final String PASTE = Constants.MPASTE_PREFIX + "/pastes/:paste_id"; // GET, DELETE + public static final String PASTE_BY_KEY = Constants.MPASTE_PREFIX + "/pastes/:paste_key"; // GET +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteLogicRouter.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteLogicRouter.java new file mode 100644 index 0000000..8b69b0d --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/routing/MPasteLogicRouter.java @@ -0,0 +1,21 @@ +package net.miarma.api.microservices.mpaste.routing; + +import io.vertx.core.Vertx; +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.http.ApiResponse; +import net.miarma.api.backlib.http.ApiStatus; +import net.miarma.api.backlib.security.SusPather; +import net.miarma.api.microservices.mpaste.handlers.PasteLogicHandler; + +public class MPasteLogicRouter { + public static void mount(Router router, Vertx vertx, Pool pool) { + PasteLogicHandler hPasteLogic = new PasteLogicHandler(vertx); + + router.route().handler(BodyHandler.create()); + + router.get(MPasteEndpoints.PASTE_BY_KEY).handler(hPasteLogic::getByKey); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/services/PasteService.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/services/PasteService.java new file mode 100644 index 0000000..f36ab27 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/services/PasteService.java @@ -0,0 +1,108 @@ +package net.miarma.api.microservices.mpaste.services; + +import java.util.List; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.Constants; +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.PasswordHasher; +import net.miarma.api.microservices.mpaste.dao.PasteDAO; +import net.miarma.api.microservices.mpaste.entities.PasteEntity; +import net.miarma.api.microservices.mpaste.validators.PasteValidator; +import net.miarma.api.backlib.util.PasteKeyGenerator; + +public class PasteService { + + private final PasteDAO pasteDAO; + private final PasteValidator pasteValidator; + + public PasteService(Pool pool) { + this.pasteDAO = new PasteDAO(pool); + this.pasteValidator = new PasteValidator(); + } + + public Future> getAll() { + return pasteDAO.getAll().compose(pastes -> { + List publicPastes = pastes.stream() + .filter(p -> !Boolean.TRUE.equals(p.getIs_private())) + .toList(); + return Future.succeededFuture(publicPastes); + }); + } + + public Future> getAll(QueryParams params) { + return pasteDAO.getAll(params).compose(pastes -> { + List publicPastes = pastes.stream() + .filter(p -> !Boolean.TRUE.equals(p.getIs_private())) + .toList(); + return Future.succeededFuture(publicPastes); + }); + } + + public Future getById(Long id) { + return pasteDAO.getById(id); + } + + public Future getByKey(String key, String password) { + return pasteDAO.getByKey(key).compose(paste -> { + if (paste == null) { + return Future.failedFuture(new NotFoundException("Paste with key " + key)); + } + if (Boolean.TRUE.equals(paste.getIs_private())) { + if (password == null || !PasswordHasher.verify(password, paste.getPassword())) { + return Future.failedFuture(new UnauthorizedException("Contraseña incorrecta")); + } + } + if (Boolean.TRUE.equals(paste.getBurn_after())) { + Vertx.vertx().setTimer(5000, _ -> { + pasteDAO.delete(paste.getPaste_id()); + }); + } + return Future.succeededFuture(paste); + }); + } + + public Future create(PasteEntity paste) { + return pasteValidator.validate(paste).compose(validation -> { + if (!validation.isValid()) { + return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors()))); + } + + String key = PasteKeyGenerator.generate(6); + paste.setPaste_key(key); + + if (paste.getPassword() != null) { + paste.setPassword(PasswordHasher.hash(paste.getPassword())); + } + + return pasteDAO.existsByKey(key).compose(exists -> { + if (exists) { + return create(paste); // recursivo, genera otra clave + } + return pasteDAO.insert(paste); + }); + }); + } + + public Future update(PasteEntity paste) { // nope + return pasteDAO.update(paste); + } + + public Future delete(Long id) { + return getById(id).compose(paste -> { + if (paste == null) { + return Future.failedFuture(new NotFoundException("Paste with id " + id)); + } + return pasteDAO.delete(id); + }); + } + + public Future exists(String key) { + return pasteDAO.existsByKey(key); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/validators/PasteValidator.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/validators/PasteValidator.java new file mode 100644 index 0000000..059b491 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/validators/PasteValidator.java @@ -0,0 +1,31 @@ +package net.miarma.api.microservices.mpaste.validators; + +import io.vertx.core.Future; +import net.miarma.api.backlib.validation.ValidationResult; +import net.miarma.api.microservices.mpaste.entities.PasteEntity; + +public class PasteValidator { + public Future validate(PasteEntity entity) { + ValidationResult result = new ValidationResult(); + + if (entity.getTitle() == null || entity.getTitle().trim().isEmpty()) { + result.addError("title", "El título no puede estar vacío"); + } + + if (entity.getContent() == null || entity.getContent().trim().isEmpty()) { + result.addError("content", "El contenido no puede estar vacío"); + } + + if (Boolean.TRUE.equals(entity.getIs_private())) { + if (entity.getPassword() == null || entity.getPassword().trim().isEmpty()) { + result.addError("password", "Las pastes privadas requieren contraseña"); + } + } + + if (entity.getTitle() != null && entity.getTitle().length() > 128) { + result.addError("title", "Título demasiado largo (128 caracteres máx.)"); + } + + return Future.succeededFuture(result); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteDataVerticle.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteDataVerticle.java new file mode 100644 index 0000000..1790dc8 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteDataVerticle.java @@ -0,0 +1,55 @@ +package net.miarma.api.microservices.mpaste.verticles; + +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.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.microservices.mpaste.routing.MPasteDataRouter; +import net.miarma.api.microservices.mpaste.services.PasteService; +import net.miarma.api.backlib.util.EventBusUtil; +import net.miarma.api.backlib.util.RouterUtil; + +public class MPasteDataVerticle extends AbstractVerticle { + private ConfigManager configManager; + private PasteService pasteService; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + + pasteService = new PasteService(pool); + + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + MPasteDataRouter.mount(router, vertx, pool); + + registerLogicVerticleConsumer(); + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("mpaste.data.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } + + private void registerLogicVerticleConsumer() { + vertx.eventBus().consumer(Constants.MPASTE_EVENT_BUS, message -> { + JsonObject body = (JsonObject) message.body(); + String action = body.getString("action"); + + switch (action) { + case "getByKey" -> { + pasteService.getByKey(body.getString("key"), body.getString("password")) + .onSuccess(paste -> message.reply(new JsonObject(Constants.GSON.toJson(paste)))) + .onFailure(EventBusUtil.fail(message)); + } + } + }); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteLogicVerticle.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteLogicVerticle.java new file mode 100644 index 0000000..d6f7e23 --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteLogicVerticle.java @@ -0,0 +1,30 @@ +package net.miarma.api.microservices.mpaste.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.ext.web.Router; +import io.vertx.sqlclient.Pool; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.db.DatabaseProvider; +import net.miarma.api.microservices.mpaste.routing.MPasteLogicRouter; +import net.miarma.api.backlib.util.RouterUtil; + +public class MPasteLogicVerticle extends AbstractVerticle { + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + configManager = ConfigManager.getInstance(); + Pool pool = DatabaseProvider.createPool(vertx, configManager); + Router router = Router.router(vertx); + RouterUtil.attachLogger(router); + MPasteLogicRouter.mount(router, vertx, pool); + + vertx.createHttpServer() + .requestHandler(router) + .listen(configManager.getIntProperty("mpaste.logic.port"), res -> { + if (res.succeeded()) startPromise.complete(); + else startPromise.fail(res.cause()); + }); + } +} diff --git a/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteMainVerticle.java b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteMainVerticle.java new file mode 100644 index 0000000..b5314ac --- /dev/null +++ b/microservices/mpaste/src/main/java/net/miarma/api/microservices/mpaste/verticles/MPasteMainVerticle.java @@ -0,0 +1,58 @@ +package net.miarma.api.microservices.mpaste.verticles; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import net.miarma.api.backlib.ConfigManager; +import net.miarma.api.backlib.Constants; +import net.miarma.api.backlib.LogAccumulator; +import net.miarma.api.backlib.util.DeploymentUtil; + +public class MPasteMainVerticle extends AbstractVerticle { + + private ConfigManager configManager; + + @Override + public void start(Promise startPromise) { + try { + this.configManager = ConfigManager.getInstance(); + deployVerticles(); + startPromise.complete(); + } catch (Exception e) { + Constants.LOGGER.error(DeploymentUtil.failMessage(MPasteMainVerticle.class, e)); + startPromise.fail(e); + } + } + + private void deployVerticles() { + vertx.deployVerticle(new MPasteDataVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(MPasteDataVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("mpaste.data.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(MPasteDataVerticle.class, result.cause())); + } + }); + + vertx.deployVerticle(new MPasteLogicVerticle(), result -> { + if (result.succeeded()) { + String message = String.join("\n\r ", + DeploymentUtil.successMessage(MPasteLogicVerticle.class), + DeploymentUtil.apiUrlMessage( + configManager.getHost(), + configManager.getIntProperty("mpaste.logic.port") + ) + ); + LogAccumulator.add(message); + } else { + LogAccumulator.add(DeploymentUtil.failMessage(MPasteLogicVerticle.class, result.cause())); + } + }); + } + +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..da54aa5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + + net.miarma.api + miarma-ecosystem + 1.2.0 + pom + + + backlib + bootstrap + microservices/huertos + microservices/huertosdecine + microservices/miarmacraft + microservices/mpaste + microservices/core + + + + + net.miarma.api + backlib + ${project.version} + + + net.miarma.api + core + ${project.version} + + + net.miarma.api + huertos + ${project.version} + + + net.miarma.api + huertosdecine + ${project.version} + + + net.miarma.api + miarmacraft + ${project.version} + + + net.miarma.api + mpaste + ${project.version} + + + + + ME-Core + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + false + + + + net.miarma.api.microservices.core.verticles.CoreMainVerticle + + + + + + + + + +