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

This commit is contained in:
Jose
2025-12-21 06:03:45 +01:00
parent 18c2f0f00b
commit 5136a67fba
105 changed files with 1506 additions and 1405 deletions

View File

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

View File

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

View File

@@ -0,0 +1,149 @@
package net.miarma.api.microservices.core.dao;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.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.core.entities.FileEntity;
public class FileDAO implements DataAccessObject<FileEntity, UUID> {
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(UUID 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(UUID 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(UUID 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(UUID id) {
Promise<Boolean> promise = Promise.promise();
FileEntity file = new FileEntity();
file.setFileId(id);
String query = QueryBuilder.delete(file).build();
db.executeOne(query, FileEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
}

View File

@@ -0,0 +1,134 @@
package net.miarma.api.microservices.core.dao;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.sqlclient.Pool;
import net.miarma.api.microservices.core.entities.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, UUID> {
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(UUID 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();
}
@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(UUID id) {
Promise<Boolean> promise = Promise.promise();
UserEntity user = new UserEntity();
user.setUserId(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(UUID id) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(UserEntity.class)
.where(Map.of("user_id", id.toString()))
.build();
db.executeOne(query, UserEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,109 @@
package net.miarma.api.microservices.core.handlers;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import org.slf4j.Logger;
import com.google.gson.Gson;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.FileUpload;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.log.LoggerProvider;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.microservices.core.entities.FileEntity;
import net.miarma.api.microservices.core.enums.CoreFileContext;
import net.miarma.api.microservices.core.services.FileService;
@SuppressWarnings("unused")
public class FileDataHandler {
private final Logger LOGGER = LoggerProvider.getLogger();
private final Gson GSON = GsonProvider.get();
private final FileService fileService;
public FileDataHandler(Pool pool) {
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) {
UUID fileId = UUID.fromString(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");
UUID uploadedBy = UUID.fromString(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.setFileName(fileName);
file.setMimeType(mimeType);
file.setUploadedBy(uploadedBy);
file.setContext(CoreFileContext.fromInt(contextValue));
fileService.create(file, fileBinary)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
}
}
public void update(RoutingContext ctx) {
FileEntity file = 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) {
UUID fileId = UUID.fromString(ctx.pathParam("file_id"));
JsonObject body = ctx.body().asJsonObject();
String filePath = body.getString("file_path");
try {
Files.deleteIfExists(Paths.get(filePath));
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
JsonUtil.sendJson(ctx, ApiStatus.INTERNAL_SERVER_ERROR, null, e.getMessage());
return;
}
fileService.delete(fileId)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
}

View File

@@ -0,0 +1,73 @@
package net.miarma.api.microservices.core.handlers;
import java.util.UUID;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.JWTManager;
import net.miarma.api.backlib.util.JsonUtil;
public class FileLogicHandler {
private final Vertx vertx;
private final String CORE_EVENT_BUS = ConfigManager.getInstance()
.getStringProperty("eventbus.core.address");
public FileLogicHandler(Vertx vertx) {
this.vertx = vertx;
}
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);
UUID userId = JWTManager.getInstance().extractUserId(token);
if (userId == null) {
JsonUtil.sendJson(ctx, ApiStatus.UNAUTHORIZED, null, "Invalid or expired token");
return false;
}
request.put("userId", userId.toString());
return true;
}
public void getUserFiles(RoutingContext ctx) {
JsonObject request = new JsonObject().put("action", "getUserFiles");
if (!validateAuth(ctx, request)) return;
vertx.eventBus().request(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) {
UUID fileId = UUID.fromString(ctx.pathParam("file_id"));
JsonObject request = new JsonObject()
.put("action", "downloadFile")
.put("fileId", fileId.toString());
if (!validateAuth(ctx, request)) return;
vertx.eventBus().request(CORE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) {
JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
} else {
JsonUtil.sendJson(ctx, ApiStatus.NOT_FOUND, null, "Error downloading file");
}
});
}
}

View File

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

View File

@@ -0,0 +1,74 @@
package net.miarma.api.microservices.core.handlers;
import com.google.gson.Gson;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.gson.GsonProvider;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.util.JsonUtil;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.services.UserService;
@SuppressWarnings("unused")
public class UserDataHandler {
private final Gson GSON = GsonProvider.get();
private final UserService userService;
public UserDataHandler(Pool pool) {
this.userService = new UserService(pool);
}
public void getAll(RoutingContext ctx) {
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 = 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 = GSON.fromJson(ctx.body().asString(), UserEntity.class);
userService.update(user)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, result)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
public void delete(RoutingContext ctx) {
Integer userId = Integer.parseInt(ctx.pathParam("user_id"));
userService.delete(userId)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, result)).onFailure(err -> {
ApiStatus status = ApiStatus.fromException(err);
JsonUtil.sendJson(ctx, status, null, err.getMessage());
});
}
}

View File

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

View File

@@ -5,9 +5,9 @@ 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.handlers.FileDataHandler;
import net.miarma.api.microservices.core.handlers.UserDataHandler;
import net.miarma.api.microservices.core.services.UserService;
import net.miarma.api.microservices.core.routing.middlewares.CoreAuthGuard;
public class CoreDataRouter {

View File

@@ -5,10 +5,10 @@ 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.handlers.FileLogicHandler;
import net.miarma.api.microservices.core.handlers.ScreenshotHandler;
import net.miarma.api.microservices.core.handlers.UserLogicHandler;
import net.miarma.api.microservices.core.services.UserService;
import net.miarma.api.microservices.core.routing.middlewares.CoreAuthGuard;
public class CoreLogicRouter {

View File

@@ -5,8 +5,8 @@ import java.util.function.Consumer;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.middlewares.AbstractAuthGuard;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.backlib.core.services.UserService;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.services.UserService;
public class CoreAuthGuard extends AbstractAuthGuard<UserEntity, CoreUserRole> {
private final UserService userService;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,13 +9,13 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.Constants.CoreUserGlobalStatus;
import net.miarma.api.backlib.Constants.CoreUserRole;
import net.miarma.api.backlib.core.entities.UserEntity;
import net.miarma.api.backlib.core.services.FileService;
import net.miarma.api.backlib.core.services.UserService;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.microservices.core.entities.UserEntity;
import net.miarma.api.microservices.core.services.FileService;
import net.miarma.api.microservices.core.services.UserService;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.RouterUtil;

View File

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

View File

@@ -4,9 +4,9 @@ import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Promise;
import io.vertx.core.ThreadingModel;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.LogAccumulator;
import net.miarma.api.backlib.config.ConfigManager;
import net.miarma.api.backlib.log.LogAccumulator;
import net.miarma.api.backlib.util.DeploymentUtil;
public class CoreMainVerticle extends AbstractVerticle {