[REPO REFACTOR]: changed to a better git repository structure with branches
This commit is contained in:
1
backlib/.gitignore
vendored
Normal file
1
backlib/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
164
backlib/pom.xml
Normal file
164
backlib/pom.xml
Normal file
@@ -0,0 +1,164 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>backlib</artifactId>
|
||||
<version>1.2.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>23</maven.compiler.source>
|
||||
<maven.compiler.target>23</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>MiarmaGit</id>
|
||||
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>gitea</id>
|
||||
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Vert.X Core -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-core</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.X Web -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.X Web Client -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web-client</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.X MariaDB/MySQL Client -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-mysql-client</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.X Mail Client -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-mail-client</artifactId>
|
||||
<version>4.5.16</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Vert.X Redis Client -->
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-redis-client</artifactId>
|
||||
<version>4.5.16</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.12.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- BCrypt -->
|
||||
<dependency>
|
||||
<groupId>org.mindrot</groupId>
|
||||
<artifactId>jbcrypt</artifactId>
|
||||
<version>0.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>4.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SLF4J + Logback -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.12</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.18.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta Mail -->
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>jakarta.mail</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Discord Webhook -->
|
||||
<dependency>
|
||||
<groupId>com.github.eduardomcb</groupId>
|
||||
<artifactId>discord-webhook</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>BackLib</finalName>
|
||||
<plugins>
|
||||
<!-- Maven Shade Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>net.miarma.backlib.MainVerticle</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
146
backlib/src/main/java/net/miarma/api/backlib/ConfigManager.java
Normal file
146
backlib/src/main/java/net/miarma/api/backlib/ConfigManager.java
Normal file
@@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
469
backlib/src/main/java/net/miarma/api/backlib/Constants.java
Normal file
469
backlib/src/main/java/net/miarma/api/backlib/Constants.java
Normal file
@@ -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<String> HUERTOS_ALLOWED_FOLDERS =
|
||||
List.of("INBOX", "Drafts", "Sent", "Spam", "Trash");
|
||||
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(Constants.APP_NAME);
|
||||
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
|
||||
.registerTypeAdapter(JsonObject.class, new JsonObjectTypeAdapter())
|
||||
.registerTypeHierarchyAdapter(ValuableEnum.class, new ValuableEnumTypeAdapter())
|
||||
.registerTypeAdapter(CoreUserGlobalStatus.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(CoreUserRole.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosUserType.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosUserStatus.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosUserRole.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosRequestStatus.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosRequestType.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosPaymentType.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosPaymentFrequency.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(HuertosAnnouncePriority.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(MMCUserStatus.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(MMCUserRole.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(CoreFileContext.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(MMCModStatus.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(CineUserRole.class, new ValuableEnumDeserializer())
|
||||
.registerTypeAdapter(CineUserStatus.class, new ValuableEnumDeserializer())
|
||||
.addSerializationExclusionStrategy(new APIDontReturnExclusionStrategy())
|
||||
.create();
|
||||
|
||||
public enum CoreUserRole implements IUserRole {
|
||||
USER(0),
|
||||
ADMIN(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
CoreUserRole(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static CoreUserRole fromInt(int i) {
|
||||
for (CoreUserRole role : values()) {
|
||||
if (role.value == i) return role;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid CoreUserRole value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum CoreUserGlobalStatus implements ValuableEnum {
|
||||
INACTIVE(0),
|
||||
ACTIVE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
CoreUserGlobalStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static CoreUserGlobalStatus fromInt(int i) {
|
||||
for (CoreUserGlobalStatus status : values()) {
|
||||
if (status.value == i) return status;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid CoreUserGlobalStatus value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CoreFileContext implements ValuableEnum {
|
||||
CORE(0),
|
||||
HUERTOS(1),
|
||||
MMC(2),
|
||||
CINE(3);
|
||||
|
||||
private final int value;
|
||||
|
||||
CoreFileContext(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String toCtxString() {
|
||||
return switch(this) {
|
||||
case CORE -> "core";
|
||||
case HUERTOS -> "huertos";
|
||||
case MMC -> "miarmacraft";
|
||||
case CINE -> "cine";
|
||||
};
|
||||
}
|
||||
|
||||
public static CoreFileContext fromInt(int i) {
|
||||
for (CoreFileContext context : values()) {
|
||||
if (context.value == i) return context;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid CoreFileContext value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum HuertosUserRole implements IUserRole {
|
||||
USER(0),
|
||||
ADMIN(1),
|
||||
DEV(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosUserRole(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosUserRole fromInt(int i) {
|
||||
for (HuertosUserRole role : values()) {
|
||||
if (role.value == i) return role;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertosUserRole value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HuertosUserType implements ValuableEnum {
|
||||
WAIT_LIST(0),
|
||||
MEMBER(1),
|
||||
WITH_GREENHOUSE(2),
|
||||
COLLABORATOR(3),
|
||||
DEVELOPER(5),
|
||||
SUBSIDY(4);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosUserType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosUserType fromInt(int i) {
|
||||
for (HuertosUserType type : values()) {
|
||||
if (type.value == i) return type;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertosUserType value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum HuertosUserStatus implements ValuableEnum {
|
||||
INACTIVE(0),
|
||||
ACTIVE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosUserStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosUserStatus fromInt(int i) {
|
||||
for (HuertosUserStatus status : values()) {
|
||||
if (status.value == i) return status;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertosUserStatus value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum HuertosRequestStatus implements ValuableEnum {
|
||||
PENDING(0),
|
||||
APPROVED(1),
|
||||
REJECTED(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosRequestStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosRequestStatus fromInt(int i) {
|
||||
for (HuertosRequestStatus status : values()) {
|
||||
if (status.value == i) return status;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertoRequestStatus value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HuertosPaymentType implements ValuableEnum {
|
||||
BANK(0),
|
||||
CASH(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosPaymentType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosPaymentType fromInt(int i) {
|
||||
for (HuertosPaymentType type : values()) {
|
||||
if (type.value == i) return type;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertoPaymentType value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HuertosRequestType implements ValuableEnum {
|
||||
REGISTER(0),
|
||||
UNREGISTER(1),
|
||||
ADD_COLLABORATOR(2),
|
||||
REMOVE_COLLABORATOR(3),
|
||||
ADD_GREENHOUSE(4),
|
||||
REMOVE_GREENHOUSE(5);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosRequestType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosRequestType fromInt(int i) {
|
||||
for (HuertosRequestType type : values()) {
|
||||
if (type.value == i) return type;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertoRequestType value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HuertosAnnouncePriority implements ValuableEnum {
|
||||
LOW(0),
|
||||
MEDIUM(1),
|
||||
HIGH(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosAnnouncePriority(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosAnnouncePriority fromInt(int i) {
|
||||
for (HuertosAnnouncePriority priority : values()) {
|
||||
if (priority.value == i) return priority;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertoAnnouncePriority value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HuertosPaymentFrequency implements ValuableEnum {
|
||||
BIYEARLY(0),
|
||||
YEARLY(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
HuertosPaymentFrequency(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static HuertosPaymentFrequency fromInt(int i) {
|
||||
for (HuertosPaymentFrequency frequency : values()) {
|
||||
if (frequency.value == i) return frequency;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid HuertoPaymentFrequency value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum MMCUserRole implements IUserRole {
|
||||
PLAYER(0),
|
||||
ADMIN(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
MMCUserRole(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static MMCUserRole fromInt(int i) {
|
||||
for (MMCUserRole role : values()) {
|
||||
if (role.value == i) return role;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid MMCUserRole value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum MMCUserStatus implements ValuableEnum {
|
||||
INACTIVE(0),
|
||||
ACTIVE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
MMCUserStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static MMCUserStatus fromInt(int i) {
|
||||
for (MMCUserStatus status : values()) {
|
||||
if (status.value == i) return status;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid MMCUserStatus value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum MMCModStatus implements ValuableEnum {
|
||||
ACTIVE(0),
|
||||
INACTIVE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
MMCModStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static MMCModStatus fromInt(int i) {
|
||||
for (MMCModStatus status : values()) {
|
||||
if (status.value == i) return status;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid MiarmacraftModStatus value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CineUserStatus implements ValuableEnum {
|
||||
ACTIVE(1),
|
||||
INACTIVE(0);
|
||||
|
||||
private final int value;
|
||||
|
||||
CineUserStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static CineUserStatus fromInt(int i) {
|
||||
for (CineUserStatus status : values()) {
|
||||
if (status.value == i) return status;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid CineUserStatus value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CineUserRole implements IUserRole {
|
||||
USER(0),
|
||||
ADMIN(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
CineUserRole(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static CineUserRole fromInt(int i) {
|
||||
for (CineUserRole role : values()) {
|
||||
if (role.value == i) return role;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid CineUserRole value: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Private constructor to prevent instantiation
|
||||
private Constants() {
|
||||
throw new AssertionError("Utility class cannot be instantiated.");
|
||||
}
|
||||
}
|
||||
@@ -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<LogEntry> 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
9
backlib/src/main/java/net/miarma/api/backlib/OSType.java
Normal file
9
backlib/src/main/java/net/miarma/api/backlib/OSType.java
Normal file
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<FileEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public FileDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<FileEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<FileEntity> getById(Integer id) {
|
||||
Promise<FileEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(FileEntity.class)
|
||||
.where(Map.of("file_id", id.toString()))
|
||||
.build();
|
||||
|
||||
db.executeOne(query, FileEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public Future<List<FileEntity>> getAll(QueryParams params) {
|
||||
Promise<List<FileEntity>> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(FileEntity.class)
|
||||
.where(params.getFilters())
|
||||
.orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder())
|
||||
.limit(params.getQueryFilters().getLimit())
|
||||
.offset(params.getQueryFilters().getOffset())
|
||||
.build();
|
||||
|
||||
db.execute(query, FileEntity.class,
|
||||
list -> promise.complete(list.isEmpty() ? List.of() : list),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public Future<List<FileEntity>> getUserFiles(Integer userId) {
|
||||
Promise<List<FileEntity>> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(FileEntity.class)
|
||||
.where(Map.of("uploaded_by", userId.toString()))
|
||||
.build();
|
||||
|
||||
db.execute(query, FileEntity.class,
|
||||
list -> promise.complete(list.isEmpty() ? List.of() : list),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<FileEntity> insert(FileEntity file) {
|
||||
Promise<FileEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.insert(file).build();
|
||||
|
||||
db.executeOne(query, FileEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<FileEntity> upsert(FileEntity file, String... conflictKeys) {
|
||||
Promise<FileEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.upsert(file, conflictKeys).build();
|
||||
|
||||
db.executeOne(query, FileEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<FileEntity> update(FileEntity file) {
|
||||
Promise<FileEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.update(file).build();
|
||||
|
||||
db.executeOne(query, FileEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(FileEntity.class)
|
||||
.where(Map.of("file_id", id.toString()))
|
||||
.build();
|
||||
|
||||
db.executeOne(query, FileEntity.class,
|
||||
result -> promise.complete(result != null),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> promise = Promise.promise();
|
||||
FileEntity file = new FileEntity();
|
||||
file.setFile_id(id);
|
||||
|
||||
String query = QueryBuilder.delete(file).build();
|
||||
|
||||
db.executeOne(query, FileEntity.class,
|
||||
result -> promise.complete(result != null),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
@@ -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<UserEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public UserDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<UserEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<UserEntity> getById(Integer id) {
|
||||
Promise<UserEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(UserEntity.class)
|
||||
.where(Map.of("user_id", id.toString()))
|
||||
.build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public Future<List<UserEntity>> getAll(QueryParams params) {
|
||||
Promise<List<UserEntity>> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(UserEntity.class)
|
||||
.where(params.getFilters())
|
||||
.orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder())
|
||||
.limit(params.getQueryFilters().getLimit())
|
||||
.offset(params.getQueryFilters().getOffset())
|
||||
.build();
|
||||
|
||||
db.execute(query, UserEntity.class,
|
||||
list -> promise.complete(list.isEmpty() ? List.of() : list),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public Future<UserEntity> getByEmail(String email) {
|
||||
Promise<UserEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(UserEntity.class)
|
||||
.where(Map.of("email", email))
|
||||
.build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public Future<UserEntity> getByUserName(String userName) {
|
||||
Promise<UserEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(UserEntity.class)
|
||||
.where(Map.of("user_name", userName))
|
||||
.build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<UserEntity> insert(UserEntity user) {
|
||||
Promise<UserEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.insert(user).build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<UserEntity> upsert(UserEntity userEntity, String... conflictKeys) {
|
||||
Promise<UserEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.upsert(userEntity, conflictKeys).build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<UserEntity> update(UserEntity user) {
|
||||
Promise<UserEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.update(user).build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
_ -> promise.complete(user),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> promise = Promise.promise();
|
||||
UserEntity user = new UserEntity();
|
||||
user.setUser_id(id);
|
||||
|
||||
String query = QueryBuilder.delete(user).build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
result -> promise.complete(result != null),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> promise = Promise.promise();
|
||||
String query = QueryBuilder
|
||||
.select(UserEntity.class)
|
||||
.where(Map.of("user_id", id.toString()))
|
||||
.build();
|
||||
|
||||
db.executeOne(query, UserEntity.class,
|
||||
result -> promise.complete(result != null),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<List<FileEntity>> getAll(QueryParams params) {
|
||||
return fileDAO.getAll(params);
|
||||
}
|
||||
|
||||
public Future<FileEntity> getById(Integer id) {
|
||||
return fileDAO.getById(id).compose(file -> {
|
||||
if (file == null) {
|
||||
return Future.failedFuture(new NotFoundException("File not found with id: " + id));
|
||||
}
|
||||
return Future.succeededFuture(file);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<List<FileEntity>> getUserFiles(Integer userId) {
|
||||
return fileDAO.getUserFiles(userId);
|
||||
}
|
||||
|
||||
public Future<FileEntity> create(FileEntity file, byte[] fileBinary) {
|
||||
return fileValidator.validate(file, fileBinary.length).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
|
||||
String dir = ConfigManager.getInstance()
|
||||
.getFilesDir(file.getContext().toCtxString());
|
||||
|
||||
String pathString = dir + file.getFile_name();
|
||||
Path filePath = Paths.get(dir + file.getFile_name());
|
||||
file.setFile_path(ConfigManager.getOS() == OSType.WINDOWS ?
|
||||
pathString.replace("\\", "\\\\") : pathString);
|
||||
|
||||
try {
|
||||
Files.write(filePath, fileBinary);
|
||||
} catch (IOException e) {
|
||||
Constants.LOGGER.error("Error writing file to disk: ", e);
|
||||
return Future.failedFuture(e);
|
||||
}
|
||||
|
||||
return fileDAO.insert(file);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<FileEntity> downloadFile(Integer fileId) {
|
||||
return getById(fileId);
|
||||
}
|
||||
|
||||
public Future<FileEntity> update(FileEntity file) {
|
||||
return fileValidator.validate(file).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
|
||||
return fileDAO.update(file);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<FileEntity> upsert(FileEntity file) {
|
||||
return fileValidator.validate(file).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
|
||||
return fileDAO.upsert(file, "file_id");
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Boolean> delete(Integer fileId) {
|
||||
return getById(fileId).compose(file -> {
|
||||
String dir = ConfigManager.getInstance()
|
||||
.getFilesDir(file.getContext().toCtxString());
|
||||
|
||||
String filePath = dir + file.getFile_name();
|
||||
Path path = Paths.get(filePath);
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
Constants.LOGGER.error("Error deleting file from disk: ", e);
|
||||
return Future.failedFuture(e);
|
||||
}
|
||||
|
||||
return fileDAO.delete(fileId).compose(deleted -> {
|
||||
if (!deleted) {
|
||||
return Future.failedFuture(new NotFoundException("File not found with id: " + fileId));
|
||||
}
|
||||
return Future.succeededFuture(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Boolean> exists(Integer fileId) {
|
||||
return fileDAO.exists(fileId);
|
||||
}
|
||||
}
|
||||
@@ -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<JsonObject> login(String emailOrUsername, String plainPassword, boolean keepLoggedIn) {
|
||||
return getByEmail(emailOrUsername).compose(user -> {
|
||||
if (user == null) {
|
||||
return getByUserName(emailOrUsername).compose(user2 -> {
|
||||
if (user2 == null) {
|
||||
return Future.succeededFuture(null);
|
||||
}
|
||||
return Future.succeededFuture(user2);
|
||||
});
|
||||
}
|
||||
return Future.succeededFuture(user);
|
||||
}).compose(user -> {
|
||||
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new BadRequestException("Invalid credentials"));
|
||||
}
|
||||
|
||||
if (user.getGlobal_status() != Constants.CoreUserGlobalStatus.ACTIVE) {
|
||||
return Future.failedFuture(new ForbiddenException("User is not active"));
|
||||
}
|
||||
|
||||
if (!PasswordHasher.verify(plainPassword, user.getPassword())) {
|
||||
return Future.failedFuture(new BadRequestException("Invalid credentials"));
|
||||
}
|
||||
|
||||
JWTManager jwtManager = JWTManager.getInstance();
|
||||
String token = jwtManager.generateToken(user.getUser_name(), user.getUser_id(), user.getGlobal_role(), keepLoggedIn);
|
||||
|
||||
JsonObject response = new JsonObject()
|
||||
.put("token", token)
|
||||
.put("loggedUser", new JsonObject(user.encode()));
|
||||
|
||||
return Future.succeededFuture(response);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<JsonObject> loginValidate(Integer userId, String password) {
|
||||
return getById(userId).compose(user -> {
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new NotFoundException("User not found"));
|
||||
}
|
||||
if (!PasswordHasher.verify(password, user.getPassword())) {
|
||||
return Future.failedFuture(new BadRequestException("Invalid credentials"));
|
||||
}
|
||||
JsonObject response = new JsonObject()
|
||||
.put("valid", true);
|
||||
return Future.succeededFuture(response);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<UserEntity> register(UserEntity user) {
|
||||
return getByEmail(user.getEmail()).compose(existing -> {
|
||||
if (existing != null) {
|
||||
return Future.failedFuture(new AlreadyExistsException("Email already exists"));
|
||||
}
|
||||
|
||||
user.setPassword(PasswordHasher.hash(user.getPassword()));
|
||||
user.setGlobal_role(CoreUserRole.USER);
|
||||
user.setGlobal_status(CoreUserGlobalStatus.ACTIVE);
|
||||
|
||||
return userValidator.validate(user).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
return userDAO.insert(user);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Future<UserEntity> changePassword(int userId, String newPassword) {
|
||||
return getById(userId).compose(user -> {
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new NotFoundException("User not found"));
|
||||
}
|
||||
|
||||
user.setPassword(PasswordHasher.hash(newPassword));
|
||||
return userDAO.update(user);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Boolean> validateToken(String token) {
|
||||
JWTManager jwtManager = JWTManager.getInstance();
|
||||
return jwtManager.isValid(token) ?
|
||||
Future.succeededFuture(true) :
|
||||
Future.failedFuture(new UnauthorizedException("Invalid token"));
|
||||
}
|
||||
|
||||
/* USERS OPERATIONS */
|
||||
|
||||
public Future<List<UserEntity>> getAll(QueryParams params) {
|
||||
return userDAO.getAll(params);
|
||||
}
|
||||
|
||||
public Future<UserEntity> getById(Integer id) {
|
||||
return userDAO.getById(id).compose(user -> {
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new NotFoundException("User not found in the database"));
|
||||
}
|
||||
return Future.succeededFuture(user);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<UserEntity> getByEmail(String email) {
|
||||
return userDAO.getByEmail(email);
|
||||
}
|
||||
|
||||
public Future<UserEntity> getByUserName(String userName) {
|
||||
return userDAO.getByUserName(userName);
|
||||
}
|
||||
|
||||
public Future<UserEntity> updateRole(Integer userId, CoreUserRole role) {
|
||||
return getById(userId).compose(user -> {
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new NotFoundException("User not found in the database"));
|
||||
}
|
||||
user.setGlobal_role(role);
|
||||
return userDAO.update(user);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<UserEntity> updateStatus(Integer userId, CoreUserGlobalStatus status) {
|
||||
return getById(userId).compose(user -> {
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new NotFoundException("User not found in the database"));
|
||||
}
|
||||
user.setGlobal_status(status);
|
||||
return userDAO.update(user);
|
||||
});
|
||||
}
|
||||
|
||||
/* CRUD OPERATIONS */
|
||||
|
||||
public Future<UserEntity> create(UserEntity user) {
|
||||
return userValidator.validate(user).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
return userDAO.insert(user);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<UserEntity> update(UserEntity user) {
|
||||
return userValidator.validate(user).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
if (user.getPassword() == null || user.getPassword().isEmpty()) {
|
||||
user.setPassword(null);
|
||||
}
|
||||
return userDAO.update(user);
|
||||
});
|
||||
}
|
||||
|
||||
public Future<UserEntity> upsert(UserEntity user) {
|
||||
return userValidator.validate(user).compose(validation -> {
|
||||
if (!validation.isValid()) {
|
||||
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
|
||||
}
|
||||
if (user.getPassword() != null && !user.getPassword().isEmpty()) {
|
||||
user.setPassword(PasswordHasher.hash(user.getPassword()));
|
||||
}
|
||||
return userDAO.upsert(user, "user_id", "email", "user_name");
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Boolean> delete(Integer id) {
|
||||
return getById(id).compose(user -> {
|
||||
if (user == null) {
|
||||
return Future.failedFuture(new NotFoundException("User not found in the database"));
|
||||
}
|
||||
return userDAO.delete(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<ValidationResult> validate(FileEntity file, int size) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (file == null) {
|
||||
return Future.succeededFuture(result.addError("file", "El archivo no puede ser nulo"));
|
||||
}
|
||||
|
||||
if (file.getFile_name() == null || file.getFile_name().isBlank()) {
|
||||
result.addError("file_name", "El nombre del archivo es obligatorio");
|
||||
}
|
||||
|
||||
if (file.getMime_type() == null || file.getMime_type().isBlank()) {
|
||||
result.addError("mime_type", "El tipo MIME es obligatorio");
|
||||
}
|
||||
|
||||
if (file.getContext() == null) {
|
||||
result.addError("context", "El contexto del archivo es obligatorio");
|
||||
}
|
||||
|
||||
if (file.getUploaded_by() == null || file.getUploaded_by() <= 0) {
|
||||
result.addError("uploaded_by", "El ID del usuario que subió el archivo es obligatorio y debe ser válido");
|
||||
}
|
||||
|
||||
if (size <= 0) {
|
||||
result.addError("size", "El archivo debe pesar más de 0 bytes");
|
||||
}
|
||||
|
||||
if (file.getFile_name() != null && file.getFile_name().length() > 255) {
|
||||
result.addError("file_name", "El nombre del archivo es demasiado largo");
|
||||
}
|
||||
|
||||
if (size > 10485760) { // 10 MB limit
|
||||
result.addError("size", "El archivo no puede pesar más de 10 MB");
|
||||
}
|
||||
|
||||
return Future.succeededFuture(result);
|
||||
}
|
||||
|
||||
public Future<ValidationResult> validate(FileEntity file) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (file == null) {
|
||||
return Future.succeededFuture(result.addError("file", "File cannot be null"));
|
||||
}
|
||||
|
||||
if (file.getFile_name() == null || file.getFile_name().isBlank()) {
|
||||
result.addError("file_name", "File name is required");
|
||||
}
|
||||
|
||||
if (file.getFile_path() == null || file.getFile_path().isBlank()) {
|
||||
result.addError("file_path", "File path is required");
|
||||
}
|
||||
|
||||
if (file.getMime_type() == null || file.getMime_type().isBlank()) {
|
||||
result.addError("mime_type", "MIME type is required");
|
||||
}
|
||||
|
||||
if (file.getContext() == null) {
|
||||
result.addError("context", "File context is required");
|
||||
}
|
||||
|
||||
if (file.getUploaded_by() == null || file.getUploaded_by() <= 0) {
|
||||
result.addError("uploaded_by", "Uploader user ID is required and must be valid");
|
||||
}
|
||||
|
||||
if (file.getFile_name() != null && file.getFile_name().length() > 255) {
|
||||
result.addError("file_name", "File name is too long");
|
||||
}
|
||||
|
||||
if (file.getFile_path() != null && file.getFile_path().length() > 255) {
|
||||
result.addError("file_path", "File path is too long");
|
||||
}
|
||||
|
||||
return Future.succeededFuture(result);
|
||||
}
|
||||
}
|
||||
@@ -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<ValidationResult> validate(UserEntity user) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
if (user == null) {
|
||||
return Future.succeededFuture(result.addError("user", "El usuario no puede ser nulo"));
|
||||
}
|
||||
|
||||
if (user.getUser_name() == null || user.getUser_name().isBlank()) {
|
||||
result.addError("user_name", "El nombre de usuario es obligatorio");
|
||||
}
|
||||
|
||||
if (user.getDisplay_name() == null || user.getDisplay_name().isBlank()) {
|
||||
result.addError("display_name", "El nombre para mostrar es obligatorio");
|
||||
}
|
||||
|
||||
if (user.getEmail() != null && !user.getEmail().isBlank()) {
|
||||
if (!user.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
|
||||
result.addError("email", "El correo electrónico no es válido");
|
||||
}
|
||||
}
|
||||
|
||||
if (user.getPassword() == null || user.getPassword().isBlank()) {
|
||||
result.addError("password", "La contraseña es obligatoria");
|
||||
}
|
||||
|
||||
if (user.getGlobal_status() == null) {
|
||||
result.addError("global_status", "El estado global del usuario es obligatorio");
|
||||
}
|
||||
|
||||
if (user.getGlobal_role() == null) {
|
||||
result.addError("role", "El rol del usuario es obligatorio");
|
||||
}
|
||||
|
||||
return Future.succeededFuture(result);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* Proporciona utilidades para:
|
||||
* <ul>
|
||||
* <li>Construir una entidad a partir de una fila de base de datos ({@link Row})</li>
|
||||
* <li>Serializar una entidad a {@link JsonObject}</li>
|
||||
* <li>Generar una representación en texto</li>
|
||||
* </ul>
|
||||
*
|
||||
* Los campos se mapean por reflexión, lo que permite extender fácilmente las entidades
|
||||
* sin necesidad de escribir lógica de parsing repetitiva.
|
||||
*
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
public abstract class AbstractEntity {
|
||||
|
||||
/**
|
||||
* Constructor por defecto. Requerido para instanciación sin datos.
|
||||
*/
|
||||
public AbstractEntity() {}
|
||||
|
||||
/**
|
||||
* Constructor que inicializa los campos de la entidad a partir de una fila de base de datos.
|
||||
*
|
||||
* @param row Fila SQL proporcionada por Vert.x.
|
||||
*/
|
||||
public AbstractEntity(Row row) {
|
||||
populateFromRow(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rellena los campos del objeto usando reflexión a partir de una {@link Row} de Vert.x.
|
||||
* Se soportan tipos básicos (String, int, boolean, etc.), enums con método estático {@code fromInt(int)},
|
||||
* y {@link java.math.BigDecimal} (a través del tipo {@code Numeric} de Vert.x).
|
||||
* <p>
|
||||
* Si un tipo no está soportado, se registra un error en el log y se ignora ese campo.
|
||||
*
|
||||
* @param row Fila de datos de la que extraer los valores.
|
||||
*/
|
||||
private 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}.
|
||||
*
|
||||
* <p>Si un campo implementa {@link ValuableEnum}, se usará su valor en lugar del nombre del enum.</p>
|
||||
*
|
||||
* @return Representación JSON de esta entidad.
|
||||
*/
|
||||
public String encode() {
|
||||
JsonObject json = new JsonObject();
|
||||
Class<?> clazz = this.getClass();
|
||||
|
||||
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.
|
||||
*
|
||||
* <p>Útil para logs y debugging.</p>
|
||||
*
|
||||
* @return Cadena de texto con el nombre de la clase y todos los campos.
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(this.getClass().getSimpleName()).append(" [ ");
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <T> Tipo de la entidad gestionada.
|
||||
* @param <ID> Tipo del identificador único de la entidad.
|
||||
*
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
public interface DataAccessObject<T, ID> {
|
||||
|
||||
/**
|
||||
* Recupera todos los registros de la entidad.
|
||||
*
|
||||
* @return Un {@link Future} que contiene una lista con todas las entidades encontradas.
|
||||
*/
|
||||
Future<List<T>> 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<T> 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<T> 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<T> 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<T> 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<Boolean> 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<Boolean> exists(ID id);
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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<RowSet<Row>> 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 <T> tipo del objeto a devolver
|
||||
* @return un {@link Future} con la lista de resultados convertidos
|
||||
*/
|
||||
public <T> Future<List<T>> execute(String query, Class<T> clazz, Handler<List<T>> onSuccess,
|
||||
Handler<Throwable> onFailure) {
|
||||
return pool.query(query).execute().map(rows -> {
|
||||
List<T> results = new ArrayList<>();
|
||||
for (Row row : rows) {
|
||||
try {
|
||||
Constructor<T> 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 <T> tipo del objeto a devolver
|
||||
* @return un {@link Future} con el objeto instanciado, o null si no hay resultados
|
||||
*/
|
||||
public <T> Future<T> executeOne(String query, Class<T> clazz, Handler<T> onSuccess, Handler<Throwable> onFailure) {
|
||||
return pool.query(query).execute().map(rows -> {
|
||||
for (Row row : rows) {
|
||||
try {
|
||||
Constructor<T> 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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:
|
||||
* <ul>
|
||||
* <li><b>db.port</b> – puerto del servidor MySQL</li>
|
||||
* <li><b>db.host</b> – host o IP</li>
|
||||
* <li><b>db.name</b> – nombre de la base de datos</li>
|
||||
* <li><b>db.user</b> – usuario de la base</li>
|
||||
* <li><b>db.password</b> – contraseña del usuario</li>
|
||||
* </ul>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
* <p>
|
||||
* Soporta operaciones SELECT, INSERT, UPDATE (con y sin valores nulos), y UPSERT.
|
||||
* También permite aplicar filtros desde un mapa o directamente desde un objeto.
|
||||
* <p>
|
||||
* ¡Ojo! No ejecuta la query, solo la construye.
|
||||
*
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
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 <T> String getTableName(Class<T> 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 <T> the type of the entity class
|
||||
*/
|
||||
public static <T> QueryBuilder select(Class<T> clazz, String... columns) {
|
||||
if (clazz == null) {
|
||||
throw new IllegalArgumentException("Class cannot be null");
|
||||
}
|
||||
|
||||
QueryBuilder qb = new QueryBuilder();
|
||||
qb.entityClass = clazz;
|
||||
String tableName = getTableName(clazz);
|
||||
|
||||
qb.query.append("SELECT ");
|
||||
|
||||
if (columns.length == 0) {
|
||||
qb.query.append("* ");
|
||||
} else {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (String column : columns) {
|
||||
if (column != null) {
|
||||
joiner.add(column);
|
||||
}
|
||||
}
|
||||
qb.query.append(joiner).append(" ");
|
||||
}
|
||||
|
||||
qb.query.append("FROM ").append(tableName).append(" ");
|
||||
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<String, String> filters) {
|
||||
if (filters == null || filters.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Set<String> validFields = entityClass != null
|
||||
? Arrays.stream(entityClass.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet())
|
||||
: Collections.emptySet();
|
||||
|
||||
List<String> conditions = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, String> entry : filters.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
if (!validFields.contains(key)) {
|
||||
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
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 <T> QueryBuilder where(T object) {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException("Object cannot be null");
|
||||
}
|
||||
|
||||
Set<String> validFields = entityClass != null
|
||||
? Arrays.stream(entityClass.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet())
|
||||
: Collections.emptySet();
|
||||
|
||||
this.query.append("WHERE ");
|
||||
StringJoiner joiner = new StringJoiner(" AND ");
|
||||
for (Field field : object.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object fieldValue = field.get(object);
|
||||
if (fieldValue != null) {
|
||||
String key = field.getName();
|
||||
if (!validFields.contains(key)) {
|
||||
Constants.LOGGER.warn("[QueryBuilder] Ignorando campo invalido en WHERE: {}", key);
|
||||
continue;
|
||||
}
|
||||
Object value = extractValue(fieldValue);
|
||||
if (value instanceof String || value instanceof LocalDateTime) {
|
||||
joiner.add(key + " = '" + value + "'");
|
||||
} else {
|
||||
joiner.add(key + " = " + value.toString());
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
this.query.append(joiner).append(" ");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye una consulta INSERT para el objeto dado, insertando todos sus campos.
|
||||
* Los valores se extraen mediante reflexión y se manejan adecuadamente.
|
||||
*
|
||||
* @param object el objeto a insertar
|
||||
* @return el QueryBuilder actual para encadenar más métodos
|
||||
* @param <T> el tipo del objeto a insertar
|
||||
*/
|
||||
public static <T> QueryBuilder insert(T object) {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException("Object cannot be null");
|
||||
}
|
||||
|
||||
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 <T> el tipo del objeto a actualizar
|
||||
*/
|
||||
public static <T> 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 <T> el tipo del objeto a actualizar
|
||||
*/
|
||||
public static <T> 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 <T> el tipo del objeto a insertar o actualizar
|
||||
*/
|
||||
public static <T> QueryBuilder upsert(T object, String... conflictKeys) {
|
||||
if (object == null) throw new IllegalArgumentException("Object cannot be null");
|
||||
|
||||
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<String, String> updates = new HashMap<>();
|
||||
|
||||
for (Field field : object.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object fieldValue = field.get(object);
|
||||
String columnName = field.getName();
|
||||
columns.add(columnName);
|
||||
|
||||
Object value = extractValue(fieldValue);
|
||||
String valueStr = value == null ? "NULL"
|
||||
: (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value.toString());
|
||||
values.add(valueStr);
|
||||
|
||||
// no actualizamos la clave duplicada
|
||||
boolean isConflictKey = Arrays.asList(conflictKeys).contains(columnName);
|
||||
if (!isConflictKey) {
|
||||
updates.put(columnName, valueStr);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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 <T> el tipo del objeto a eliminar
|
||||
*/
|
||||
public static <T> QueryBuilder delete(T object) {
|
||||
if (object == null) throw new IllegalArgumentException("Object cannot be null");
|
||||
|
||||
QueryBuilder qb = new QueryBuilder();
|
||||
String table = getTableName(object.getClass());
|
||||
qb.query.append("DELETE FROM ").append(table).append(" WHERE ");
|
||||
|
||||
StringJoiner joiner = new StringJoiner(" AND ");
|
||||
for (Field field : object.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object fieldValue = field.get(object);
|
||||
if (fieldValue != null) {
|
||||
Object value = extractValue(fieldValue);
|
||||
joiner.add(field.getName() + " = " + (value instanceof String
|
||||
|| value instanceof LocalDateTime ? "'" + value + "'" : value.toString()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Constants.LOGGER.error("(REFLECTION) Error reading field: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
qb.query.append(joiner).append(" ");
|
||||
return qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Añade una cláusula ORDER BY a la consulta actual, ordenando por la columna y el orden especificados.
|
||||
* Si la columna no es válida, se ignora.
|
||||
*
|
||||
* @param column la columna por la que ordenar
|
||||
* @param order el orden (ASC o DESC); si no se especifica, se asume ASC
|
||||
* @return el QueryBuilder actual para encadenar más métodos
|
||||
*/
|
||||
public QueryBuilder orderBy(Optional<String> column, Optional<String> order) {
|
||||
column.ifPresent(c -> {
|
||||
if (entityClass != null) {
|
||||
boolean isValid = Arrays.stream(entityClass.getDeclaredFields())
|
||||
.map(Field::getName)
|
||||
.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<Integer> 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<Integer> 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() + ";";
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p>Esta excepción es de tipo {@link RuntimeException}, por lo que no es necesario
|
||||
* declararla explícitamente en los métodos que la lanzan.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<JsonObject>, JsonDeserializer<JsonObject> {
|
||||
|
||||
@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<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
|
||||
obj.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -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<LocalDateTime>, JsonDeserializer<LocalDateTime> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ValuableEnum> {
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
@@ -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<ValuableEnum> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ValuableEnum src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.miarma.api.backlib.http;
|
||||
|
||||
/**
|
||||
* Clase genérica para representar una respuesta de la API.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Ejemplo de uso:
|
||||
* <pre>
|
||||
* ApiResponse<MyDataType> response = new ApiResponse<>(ApiStatus.SUCCESS, "Data retrieved successfully", myData);
|
||||
* </pre>
|
||||
* @see ApiStatus
|
||||
*
|
||||
* @param <T> Tipo de dato que contendrá la respuesta.
|
||||
*
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
public class ApiResponse<T> {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
108
backlib/src/main/java/net/miarma/api/backlib/http/ApiStatus.java
Normal file
108
backlib/src/main/java/net/miarma/api/backlib/http/ApiStatus.java
Normal file
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> sort = Optional.empty();
|
||||
private Optional<String> order = Optional.of("ASC");
|
||||
private Optional<Integer> limit = Optional.empty();
|
||||
private Optional<Integer> offset = Optional.empty();
|
||||
|
||||
public QueryFilters() {}
|
||||
|
||||
public QueryFilters(Optional<String> sort, Optional<String> order, Optional<Integer> limit, Optional<Integer> offset) {
|
||||
this.sort = sort;
|
||||
this.order = order;
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public Optional<String> getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public void setSort(String sort) {
|
||||
this.sort = Optional.ofNullable(sort);
|
||||
}
|
||||
|
||||
public Optional<String> getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(String order) {
|
||||
this.order = Optional.ofNullable(order);
|
||||
}
|
||||
|
||||
public Optional<Integer> getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = Optional.ofNullable(limit);
|
||||
}
|
||||
|
||||
public Optional<Integer> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> filters;
|
||||
private final QueryFilters queryFilters;
|
||||
|
||||
public QueryParams(Map<String, String> filters, QueryFilters queryFilters) {
|
||||
this.filters = filters;
|
||||
this.queryFilters = queryFilters;
|
||||
}
|
||||
|
||||
public Map<String, String> getFilters() {
|
||||
return filters;
|
||||
}
|
||||
|
||||
public QueryFilters getQueryFilters() {
|
||||
return queryFilters;
|
||||
}
|
||||
|
||||
public static QueryParams from(RoutingContext ctx) {
|
||||
Map<String, String> 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<String> validKeys = getFieldNames(entityClass);
|
||||
|
||||
Map<String, String> 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<String> getFieldNames(Class<?> clazz) {
|
||||
return Arrays.stream(clazz.getDeclaredFields())
|
||||
.map(Field::getName)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QueryParams{" +
|
||||
"filters=" + filters +
|
||||
", queryFilters=" + queryFilters +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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 <T> el tipo del mensaje que se envía en la respuesta
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
public record SingleJsonResponse<T>(T message) {
|
||||
public static <T> SingleJsonResponse<T> of(T message) {
|
||||
return new SingleJsonResponse<>(message);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.miarma.api.backlib.interfaces;
|
||||
|
||||
import net.miarma.api.backlib.ValuableEnum;
|
||||
|
||||
public interface IUserRole extends ValuableEnum {
|
||||
String name();
|
||||
}
|
||||
@@ -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<U, R extends Enum<R> & IUserRole> {
|
||||
|
||||
protected abstract R parseRole(String roleStr);
|
||||
protected abstract void getUserEntity(int userId, RoutingContext ctx, Consumer<U> callback);
|
||||
protected abstract boolean hasPermission(U user, R role);
|
||||
|
||||
public Handler<RoutingContext> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package net.miarma.api.backlib.security;
|
||||
|
||||
/**
|
||||
* Validador de DNI/NIE español.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* Esta clase sigue el patron Singleton para asegurar una sola instancia.
|
||||
*
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
public class JWTManager {
|
||||
|
||||
private final ConfigManager config = ConfigManager.getInstance();
|
||||
private final 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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<String> 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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 <T> String successMessage(Class<T> clazz) {
|
||||
return String.join(" ", "🟢", clazz.getSimpleName(), "deployed successfully");
|
||||
}
|
||||
|
||||
public static <T> String failMessage(Class<T> 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);
|
||||
}
|
||||
}
|
||||
@@ -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 <T> Handler<Throwable> fail(Message<T> 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 <T> Handler<Throwable> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <T> void sendJson(RoutingContext ctx, ApiStatus status, T data) {
|
||||
sendJson(ctx, status, data, status.getDefaultMessage());
|
||||
}
|
||||
|
||||
public static <T> 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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<String, UserRequests> 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;
|
||||
}
|
||||
}
|
||||
@@ -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 "📥";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, String> errors = new HashMap<>();
|
||||
|
||||
public ValidationResult addError(String field, String message) {
|
||||
errors.put(field, message);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return errors.isEmpty();
|
||||
}
|
||||
|
||||
public Map<String, String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public String getFirstError() {
|
||||
return errors.values().stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.miarma.api.backlib.validation;
|
||||
|
||||
import io.vertx.core.Future;
|
||||
|
||||
/**
|
||||
* Interfaz para la validación de entidades.
|
||||
* @param <T> Tipo de entidad a validar.
|
||||
* @author José Manuel Amador Gallardo
|
||||
*/
|
||||
public interface Validator<T> {
|
||||
Future<ValidationResult> validate(T entity);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
1
bootstrap/.gitignore
vendored
Normal file
1
bootstrap/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
90
bootstrap/pom.xml
Normal file
90
bootstrap/pom.xml
Normal file
@@ -0,0 +1,90 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>miarma-ecosystem</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>bootstrap</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>23</maven.compiler.source>
|
||||
<maven.compiler.target>23</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>MiarmaGit</id>
|
||||
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>backlib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>huertos</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>huertosdecine</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>miarmacraft</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>mpaste</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>MiarmaEcosystem</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>net.miarma.api.AppInitializer</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
75
bootstrap/src/main/java/net/miarma/api/AppInitializer.java
Normal file
75
bootstrap/src/main/java/net/miarma/api/AppInitializer.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
66
bootstrap/src/main/java/net/miarma/api/MasterVerticle.java
Normal file
66
bootstrap/src/main/java/net/miarma/api/MasterVerticle.java
Normal file
@@ -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<Void> startPromise) {
|
||||
deploy()
|
||||
.onSuccess(v -> {
|
||||
vertx.setTimer(300, id -> {
|
||||
LogAccumulator.flushToLogger(Constants.LOGGER);
|
||||
startPromise.complete();
|
||||
});
|
||||
})
|
||||
.onFailure(startPromise::fail);
|
||||
}
|
||||
|
||||
private Future<Void> deploy() {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
|
||||
Future<String> core = vertx.deployVerticle(new CoreMainVerticle())
|
||||
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(CoreMainVerticle.class)))
|
||||
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(CoreMainVerticle.class, err)));
|
||||
|
||||
Future<String> huertos = vertx.deployVerticle(new HuertosMainVerticle())
|
||||
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(HuertosMainVerticle.class)))
|
||||
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(HuertosMainVerticle.class, err)));
|
||||
|
||||
Future<String> mmc = vertx.deployVerticle(new MMCMainVerticle())
|
||||
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(MMCMainVerticle.class)))
|
||||
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(MMCMainVerticle.class, err)));
|
||||
|
||||
Future<String> cine = vertx.deployVerticle(new CineMainVerticle())
|
||||
.onSuccess(id -> LogAccumulator.add(DeploymentUtil.successMessage(CineMainVerticle.class)))
|
||||
.onFailure(err -> LogAccumulator.add(DeploymentUtil.failMessage(CineMainVerticle.class, err)));
|
||||
|
||||
Future<String> 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<Void> stopPromise) {
|
||||
vertx.deploymentIDs().forEach(id -> vertx.undeploy(id));
|
||||
stopPromise.complete();
|
||||
}
|
||||
}
|
||||
41
bootstrap/src/main/resources/default.properties
Normal file
41
bootstrap/src/main/resources/default.properties
Normal file
@@ -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=
|
||||
20
bootstrap/src/main/resources/logback.xml
Normal file
20
bootstrap/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>
|
||||
%cyan([%d{HH:mm:ss}]) %highlight(%-5level) %green(%logger{20}) - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="io.netty" level="WARN"/>
|
||||
<logger name="io.vertx" level="INFO"/>
|
||||
<logger name="io.vertx.core.impl.launcher" level="INFO"/>
|
||||
<logger name="io.vertx.core.logging" level="INFO"/>
|
||||
|
||||
</configuration>
|
||||
1
microservices/core/.gitignore
vendored
Normal file
1
microservices/core/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
54
microservices/core/pom.xml
Normal file
54
microservices/core/pom.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.2.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>23</maven.compiler.source>
|
||||
<maven.compiler.target>23</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>MiarmaGit</id>
|
||||
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>backlib</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>ME-Core</finalName>
|
||||
<plugins>
|
||||
<!-- Maven Shade Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>net.miarma.api.microservices.core.verticles.CoreMainVerticle</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<UserEntity, CoreUserRole> {
|
||||
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<UserEntity> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> 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<String, Object> result = new HashMap<>();
|
||||
result.put("user_id", userId);
|
||||
result.put("exists", user != null);
|
||||
message.reply(result);
|
||||
})
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
}
|
||||
|
||||
case "getByEmail" -> userService.getByEmail(body.getString("email"))
|
||||
.onSuccess(message::reply)
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "getByUserName" -> userService.getByUserName(body.getString("userName"))
|
||||
.onSuccess(message::reply)
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "getStatus" -> userService.getById(body.getInteger("userId"))
|
||||
.onSuccess(user -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("user_id", user.getUser_id());
|
||||
result.put("status", user.getGlobal_status());
|
||||
message.reply(result);
|
||||
})
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "getRole" -> userService.getById(body.getInteger("userId"))
|
||||
.onSuccess(user -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("user_id", user.getUser_id());
|
||||
result.put("role", user.getGlobal_role());
|
||||
message.reply(result);
|
||||
})
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "getAvatar" -> userService.getById(body.getInteger("userId"))
|
||||
.onSuccess(user -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("user_id", user.getUser_id());
|
||||
result.put("avatar", user.getAvatar());
|
||||
message.reply(result);
|
||||
})
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "updateStatus" -> userService.updateStatus(
|
||||
body.getInteger("userId"),
|
||||
CoreUserGlobalStatus.fromInt(body.getInteger("status")))
|
||||
.onSuccess(res -> message.reply("Status updated successfully"))
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "updateRole" -> userService.updateRole(
|
||||
body.getInteger("userId"),
|
||||
CoreUserRole.fromInt(body.getInteger("role")))
|
||||
.onSuccess(res -> message.reply("Role updated successfully"))
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "getUserFiles" -> fileService.getUserFiles(body.getInteger("userId"))
|
||||
.onSuccess(message::reply)
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "downloadFile" -> fileService.downloadFile(body.getInteger("fileId"))
|
||||
.onSuccess(message::reply)
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "getUserById" -> userService.getById(body.getInteger("userId"))
|
||||
.onSuccess(user -> {
|
||||
String userJson = Constants.GSON.toJson(user);
|
||||
message.reply(new JsonObject(userJson));
|
||||
})
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
|
||||
case "loginValidate" -> {
|
||||
Integer userId = body.getInteger("userId");
|
||||
String password = body.getString("password");
|
||||
|
||||
userService.loginValidate(userId, password)
|
||||
.onSuccess(user -> {
|
||||
String userJson = Constants.GSON.toJson(user);
|
||||
message.reply(new JsonObject(userJson));
|
||||
})
|
||||
.onFailure(EventBusUtil.fail(message));
|
||||
}
|
||||
|
||||
default -> EventBusUtil.fail(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<Void> 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<Void> 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()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
1
microservices/huertos/.gitignore
vendored
Normal file
1
microservices/huertos/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
55
microservices/huertos/pom.xml
Normal file
55
microservices/huertos/pom.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>huertos</artifactId>
|
||||
<version>1.2.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>23</maven.compiler.source>
|
||||
<maven.compiler.target>23</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>MiarmaGit</id>
|
||||
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.miarma.api</groupId>
|
||||
<artifactId>backlib</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>ME-Huertos</finalName>
|
||||
<plugins>
|
||||
<!-- Maven Shade Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>net.miarma.api.microservices.huertos.HuertosMainVerticle</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -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<AnnouncementEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public AnnouncementDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<AnnouncementEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<AnnouncementEntity> getById(Integer id) {
|
||||
Promise<AnnouncementEntity> 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<List<AnnouncementEntity>> getAll(QueryParams params) {
|
||||
Promise<List<AnnouncementEntity>> 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<AnnouncementEntity> insert(AnnouncementEntity announce) {
|
||||
Promise<AnnouncementEntity> 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<AnnouncementEntity> upsert(AnnouncementEntity announcementEntity, String... conflictKeys) {
|
||||
Promise<AnnouncementEntity> 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<AnnouncementEntity> update(AnnouncementEntity announce) {
|
||||
Promise<AnnouncementEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.update(announce).build();
|
||||
|
||||
db.executeOne(query, AnnouncementEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<BalanceEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public BalanceDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<BalanceEntity>> getAll() {
|
||||
Promise<List<BalanceEntity>> 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<BalanceEntity> getById(Integer id) {
|
||||
Promise<BalanceEntity> 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<List<ViewBalanceWithTotals>> getAllWithTotals() {
|
||||
Promise<List<ViewBalanceWithTotals>> 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<BalanceEntity> insert(BalanceEntity balance) {
|
||||
Promise<BalanceEntity> 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<BalanceEntity> upsert(BalanceEntity balanceEntity, String... conflictKeys) {
|
||||
Promise<BalanceEntity> 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<BalanceEntity> update(BalanceEntity balance) {
|
||||
Promise<BalanceEntity> 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<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<ExpenseEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public ExpenseDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<ExpenseEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<ExpenseEntity> getById(Integer id) {
|
||||
Promise<ExpenseEntity> 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<List<ExpenseEntity>> getAll(QueryParams params) {
|
||||
Promise<List<ExpenseEntity>> 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<ExpenseEntity> insert(ExpenseEntity expense) {
|
||||
Promise<ExpenseEntity> 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<ExpenseEntity> upsert(ExpenseEntity expenseEntity, String... conflictKeys) {
|
||||
Promise<ExpenseEntity> 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<ExpenseEntity> update(ExpenseEntity expense) {
|
||||
Promise<ExpenseEntity> 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<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<IncomeEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public IncomeDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<IncomeEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<IncomeEntity> getById(Integer id) {
|
||||
Promise<IncomeEntity> 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<List<IncomeEntity>> getAll(QueryParams params) {
|
||||
Promise<List<IncomeEntity>> 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<List<ViewIncomesWithFullNames>> getAllWithNames() {
|
||||
return getAllWithNames(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
public Future<List<ViewIncomesWithFullNames>> getAllWithNames(QueryParams params) {
|
||||
Promise<List<ViewIncomesWithFullNames>> 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<List<IncomeEntity>> getUserIncomes(Integer memberNumber) {
|
||||
Promise<List<IncomeEntity>> 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<IncomeEntity> insert(IncomeEntity income) {
|
||||
Promise<IncomeEntity> 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<IncomeEntity> upsert(IncomeEntity incomeEntity, String... conflictKeys) {
|
||||
Promise<IncomeEntity> 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<IncomeEntity> update(IncomeEntity income) {
|
||||
Promise<IncomeEntity> 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<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<MemberEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public MemberDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<MemberEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<MemberEntity> getById(Integer id) {
|
||||
Promise<MemberEntity> 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<List<MemberEntity>> getAll(QueryParams params) {
|
||||
Promise<List<MemberEntity>> 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<MemberEntity> getByMemberNumber(Integer memberNumber) {
|
||||
Promise<MemberEntity> 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<MemberEntity> getByPlotNumber(Integer plotNumber) {
|
||||
Promise<MemberEntity> 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<MemberEntity> getByEmail(String email) {
|
||||
Promise<MemberEntity> 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<MemberEntity> getByDni(String dni) {
|
||||
Promise<MemberEntity> 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<MemberEntity> getByPhone(Integer phone) {
|
||||
Promise<MemberEntity> 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<List<MemberEntity>> getWaitlist() {
|
||||
Promise<List<MemberEntity>> 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<Integer> getLastMemberNumber() {
|
||||
Promise<Integer> 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<Boolean> hasCollaborator(Integer plotNumber) {
|
||||
Promise<Boolean> 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<MemberEntity> getCollaborator(Integer plotNumber) {
|
||||
Promise<MemberEntity> 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<MemberEntity> insert(MemberEntity user) {
|
||||
throw new UnsupportedOperationException("Insert not supported on view-based DAO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<MemberEntity> upsert(MemberEntity memberEntity, String... conflictKeys) {
|
||||
throw new UnsupportedOperationException("Upsert not supported on view-based DAO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<MemberEntity> update(MemberEntity user) {
|
||||
throw new UnsupportedOperationException("Update not supported on view-based DAO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> delete(Integer id) {
|
||||
throw new UnsupportedOperationException("Delete not supported on view-based DAO");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<PreUserEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public PreUserDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<PreUserEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<PreUserEntity> getById(Integer id) {
|
||||
Promise<PreUserEntity> 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<PreUserEntity> getByRequestId(Integer requestId) {
|
||||
Promise<PreUserEntity> 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<List<PreUserEntity>> getAll(QueryParams params) {
|
||||
Promise<List<PreUserEntity>> 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<PreUserEntity> insert(PreUserEntity preUser) {
|
||||
Promise<PreUserEntity> 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<PreUserEntity> upsert(PreUserEntity preUserEntity, String... conflictKeys) {
|
||||
Promise<PreUserEntity> 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<PreUserEntity> update(PreUserEntity preUser) {
|
||||
Promise<PreUserEntity> 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<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<RequestEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public RequestDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<RequestEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<RequestEntity> getById(Integer id) {
|
||||
Promise<RequestEntity> 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<List<RequestEntity>> getAll(QueryParams params) {
|
||||
Promise<List<RequestEntity>> 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<List<ViewRequestsWithPreUsers>> getRequestsWithPreUsers() {
|
||||
Promise<List<ViewRequestsWithPreUsers>> 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<ViewRequestsWithPreUsers> getRequestWithPreUserById(Integer id) {
|
||||
Promise<ViewRequestsWithPreUsers> 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<List<RequestEntity>> getByUserId(Integer userId) {
|
||||
Promise<List<RequestEntity>> 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<RequestEntity> insert(RequestEntity request) {
|
||||
Promise<RequestEntity> 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<RequestEntity> upsert(RequestEntity requestEntity, String... conflictKeys) {
|
||||
Promise<RequestEntity> 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<RequestEntity> update(RequestEntity request) {
|
||||
Promise<RequestEntity> 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<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<UserMetadataEntity, Integer> {
|
||||
|
||||
private final DatabaseManager db;
|
||||
|
||||
public UserMetadataDAO(Pool pool) {
|
||||
this.db = DatabaseManager.getInstance(pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<List<UserMetadataEntity>> getAll() {
|
||||
return getAll(new QueryParams(Map.of(), new QueryFilters()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<UserMetadataEntity> getById(Integer id) {
|
||||
Promise<UserMetadataEntity> 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<List<UserMetadataEntity>> getAll(QueryParams params) {
|
||||
Promise<List<UserMetadataEntity>> 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<UserMetadataEntity> insert(UserMetadataEntity user) {
|
||||
Promise<UserMetadataEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.insert(user).build();
|
||||
|
||||
db.executeOne(query, UserMetadataEntity.class,
|
||||
promise::complete,
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<UserMetadataEntity> upsert(UserMetadataEntity userMetadataEntity, String... conflictKeys) {
|
||||
Promise<UserMetadataEntity> 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<UserMetadataEntity> update(UserMetadataEntity user) {
|
||||
Promise<UserMetadataEntity> promise = Promise.promise();
|
||||
String query = QueryBuilder.update(user).build();
|
||||
|
||||
db.executeOne(query, UserMetadataEntity.class,
|
||||
_ -> promise.complete(user),
|
||||
promise::fail
|
||||
);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public Future<UserMetadataEntity> updateWithNulls(UserMetadataEntity user) {
|
||||
Promise<UserMetadataEntity> 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<Boolean> delete(Integer id) {
|
||||
Promise<Boolean> 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<Boolean> exists(Integer id) {
|
||||
Promise<Boolean> 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user