[REPO REFACTOR]: changed to a better git repository structure with branches

This commit is contained in:
2025-10-31 03:32:24 +01:00
parent ad689049d5
commit 8360c7e8e0
212 changed files with 15955 additions and 0 deletions

1
microservices/mpaste/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target/

View 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>mpaste</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-MPaste</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.mpaste.MPasteMainVerticle</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,124 @@
package net.miarma.api.microservices.mpaste.dao;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.db.DataAccessObject;
import net.miarma.api.backlib.db.DatabaseManager;
import net.miarma.api.backlib.db.QueryBuilder;
import net.miarma.api.backlib.http.QueryFilters;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.mpaste.entities.PasteEntity;
import java.util.List;
import java.util.Map;
public class PasteDAO implements DataAccessObject<PasteEntity, Long> {
private final DatabaseManager db;
public PasteDAO(Pool pool) {
this.db = DatabaseManager.getInstance(pool);
}
@Override
public Future<List<PasteEntity>> getAll() {
return getAll(new QueryParams(Map.of(), new QueryFilters()));
}
public Future<List<PasteEntity>> getAll(QueryParams params) {
Promise<List<PasteEntity>> promise = Promise.promise();
String query = QueryBuilder
.select(PasteEntity.class)
.where(params.getFilters())
.orderBy(params.getQueryFilters().getSort(), params.getQueryFilters().getOrder())
.limit(params.getQueryFilters().getLimit())
.offset(params.getQueryFilters().getOffset())
.build();
db.execute(query, PasteEntity.class,
list -> promise.complete(list.isEmpty() ? List.of() : list),
promise::fail
);
return promise.future();
}
@Override
public Future<PasteEntity> getById(Long id) {
throw new UnsupportedOperationException("You cannot get this type by its ID");
}
public Future<PasteEntity> getByKey(String key) {
Promise<PasteEntity> promise = Promise.promise();
String query = QueryBuilder
.select(PasteEntity.class)
.where(Map.of("paste_key", key))
.build();
db.executeOne(query, PasteEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<PasteEntity> insert(PasteEntity paste) {
Promise<PasteEntity> promise = Promise.promise();
String query = QueryBuilder.insert(paste).build();
db.executeOne(query, PasteEntity.class,
promise::complete,
promise::fail
);
return promise.future();
}
@Override
public Future<PasteEntity> upsert(PasteEntity paste, String... conflictKeys) {
throw new UnsupportedOperationException("Upsert not supported on this type");
}
@Override
public Future<PasteEntity> update(PasteEntity paste) {
throw new UnsupportedOperationException("Update not supported on this type");
}
@Override
public Future<Boolean> delete(Long id) {
Promise<Boolean> promise = Promise.promise();
PasteEntity paste = new PasteEntity();
paste.setPaste_id(id);
String query = QueryBuilder
.delete(paste)
.build();
db.executeOne(query, PasteEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
@Override
public Future<Boolean> exists(Long id) {
throw new UnsupportedOperationException("You cannot check existance on this type by its ID");
}
public Future<Boolean> existsByKey(String key) {
Promise<Boolean> promise = Promise.promise();
String query = QueryBuilder
.select(PasteEntity.class)
.where(Map.of("paste_key", key))
.build();
db.executeOne(query, PasteEntity.class,
result -> promise.complete(result != null),
promise::fail
);
return promise.future();
}
}

View File

@@ -0,0 +1,129 @@
package net.miarma.api.microservices.mpaste.entities;
import java.time.LocalDateTime;
import io.vertx.sqlclient.Row;
import net.miarma.api.backlib.annotations.APIDontReturn;
import net.miarma.api.backlib.annotations.Table;
import net.miarma.api.backlib.db.AbstractEntity;
@Table("mpaste_pastes")
public class PasteEntity extends AbstractEntity {
private Long paste_id;
private String paste_key;
private String title;
private String content;
private String syntax;
private LocalDateTime created_at;
private LocalDateTime expires_at;
private Integer views;
private Boolean burn_after;
private Boolean is_private;
@APIDontReturn
private String password;
private Integer owner_id;
public PasteEntity() {
super();
}
public PasteEntity(Row row) {
super(row);
}
public Long getPaste_id() {
return paste_id;
}
public void setPaste_id(Long paste_id) {
this.paste_id = paste_id;
}
public String getPaste_key() {
return paste_key;
}
public void setPaste_key(String paste_key) {
this.paste_key = paste_key;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSyntax() {
return syntax;
}
public void setSyntax(String syntax) {
this.syntax = syntax;
}
public LocalDateTime getCreated_at() {
return created_at;
}
public void setCreated_at(LocalDateTime created_at) {
this.created_at = created_at;
}
public LocalDateTime getExpires_at() {
return expires_at;
}
public void setExpires_at(LocalDateTime expires_at) {
this.expires_at = expires_at;
}
public Integer getViews() {
return views;
}
public void setViews(Integer views) {
this.views = views;
}
public Boolean getBurn_after() {
return burn_after;
}
public void setBurn_after(Boolean burn_after) {
this.burn_after = burn_after;
}
public Boolean getIs_private() {
return is_private;
}
public void setIs_private(Boolean is_private) {
this.is_private = is_private;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getOwner_id() {
return owner_id;
}
public void setOwner_id(Integer owner_id) {
this.owner_id = owner_id;
}
}

View File

@@ -0,0 +1,57 @@
package net.miarma.api.microservices.mpaste.handlers;
import io.vertx.ext.web.RoutingContext;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.microservices.mpaste.entities.PasteEntity;
import net.miarma.api.microservices.mpaste.services.PasteService;
import net.miarma.api.backlib.util.JsonUtil;
@SuppressWarnings("unused")
public class PasteDataHandler {
private final PasteService pasteService;
public PasteDataHandler(Pool pool) {
this.pasteService = new PasteService(pool);
}
public void getAll(RoutingContext ctx) {
QueryParams params = QueryParams.from(ctx);
pasteService.getAll(params)
.onSuccess(pastes -> JsonUtil.sendJson(ctx, ApiStatus.OK, pastes))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void getById(RoutingContext ctx) {
Long pasteId = Long.parseLong(ctx.pathParam("paste_id"));
pasteService.getById(pasteId)
.onSuccess(paste -> JsonUtil.sendJson(ctx, ApiStatus.OK, paste))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void create(RoutingContext ctx) {
PasteEntity paste = Constants.GSON.fromJson(ctx.body().asString(), PasteEntity.class);
pasteService.create(paste)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.CREATED, result))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
public void update(RoutingContext ctx) {
PasteEntity paste = Constants.GSON.fromJson(ctx.body().asString(), PasteEntity.class);
pasteService.update(paste);
}
public void delete(RoutingContext ctx) {
Long pasteId = Long.parseLong(ctx.pathParam("paste_id"));
pasteService.delete(pasteId)
.onSuccess(result -> JsonUtil.sendJson(ctx, ApiStatus.NO_CONTENT, null))
.onFailure(err -> JsonUtil.sendJson(ctx, ApiStatus.fromException(err), null, err.getMessage()));
}
}

View File

@@ -0,0 +1,31 @@
package net.miarma.api.microservices.mpaste.handlers;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.JsonUtil;
public class PasteLogicHandler {
private final Vertx vertx;
public PasteLogicHandler(Vertx vertx) {
this.vertx = vertx;
}
public void getByKey(RoutingContext ctx) {
String key = ctx.request().getParam("paste_key");
String password = ctx.request().getHeader("X-Paste-Password");
JsonObject request = new JsonObject()
.put("action", "getByKey")
.put("key", key)
.put("password", password);
vertx.eventBus().request(Constants.MPASTE_EVENT_BUS, request, ar -> {
if (ar.succeeded()) JsonUtil.sendJson(ctx, ApiStatus.OK, ar.result().body());
else EventBusUtil.handleReplyError(ctx, ar.cause(), "Paste not found");
});
}
}

View File

@@ -0,0 +1,37 @@
package net.miarma.api.microservices.mpaste.routing;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.http.ApiResponse;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.SusPather;
import net.miarma.api.microservices.mpaste.handlers.PasteDataHandler;
import net.miarma.api.backlib.util.RateLimiter;
public class MPasteDataRouter {
private static RateLimiter limiter = new RateLimiter();
public static void mount(Router router, Vertx vertx, Pool pool) {
PasteDataHandler hPasteData = new PasteDataHandler(pool);
router.route().handler(BodyHandler.create());
router.get(MPasteEndpoints.PASTES).handler(hPasteData::getAll);
router.post(MPasteEndpoints.PASTES).handler(ctx -> {
String ip = ctx.request().remoteAddress().host();
if (!limiter.allow(ip)) {
ApiResponse<JsonObject> response = new ApiResponse<>(ApiStatus.TOO_MANY_REQUESTS, "Too many requests", null);
JsonObject jsonResponse = new JsonObject().put("status", response.getStatus()).put("message",
response.getMessage());
ctx.response().setStatusCode(response.getStatus()).putHeader("Content-Type", "application/json")
.end(jsonResponse.encode());
}
hPasteData.create(ctx);
});
router.get(MPasteEndpoints.PASTE).handler(hPasteData::getById);
router.delete(MPasteEndpoints.PASTE).handler(hPasteData::delete);
}
}

View File

@@ -0,0 +1,9 @@
package net.miarma.api.microservices.mpaste.routing;
import net.miarma.api.backlib.Constants;
public class MPasteEndpoints {
public static final String PASTES = Constants.MPASTE_PREFIX + "/pastes"; // GET, POST
public static final String PASTE = Constants.MPASTE_PREFIX + "/pastes/:paste_id"; // GET, DELETE
public static final String PASTE_BY_KEY = Constants.MPASTE_PREFIX + "/pastes/:paste_key"; // GET
}

View File

@@ -0,0 +1,21 @@
package net.miarma.api.microservices.mpaste.routing;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.http.ApiResponse;
import net.miarma.api.backlib.http.ApiStatus;
import net.miarma.api.backlib.security.SusPather;
import net.miarma.api.microservices.mpaste.handlers.PasteLogicHandler;
public class MPasteLogicRouter {
public static void mount(Router router, Vertx vertx, Pool pool) {
PasteLogicHandler hPasteLogic = new PasteLogicHandler(vertx);
router.route().handler(BodyHandler.create());
router.get(MPasteEndpoints.PASTE_BY_KEY).handler(hPasteLogic::getByKey);
}
}

View File

@@ -0,0 +1,108 @@
package net.miarma.api.microservices.mpaste.services;
import java.util.List;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.exceptions.NotFoundException;
import net.miarma.api.backlib.exceptions.UnauthorizedException;
import net.miarma.api.backlib.exceptions.ValidationException;
import net.miarma.api.backlib.http.QueryParams;
import net.miarma.api.backlib.security.PasswordHasher;
import net.miarma.api.microservices.mpaste.dao.PasteDAO;
import net.miarma.api.microservices.mpaste.entities.PasteEntity;
import net.miarma.api.microservices.mpaste.validators.PasteValidator;
import net.miarma.api.backlib.util.PasteKeyGenerator;
public class PasteService {
private final PasteDAO pasteDAO;
private final PasteValidator pasteValidator;
public PasteService(Pool pool) {
this.pasteDAO = new PasteDAO(pool);
this.pasteValidator = new PasteValidator();
}
public Future<List<PasteEntity>> getAll() {
return pasteDAO.getAll().compose(pastes -> {
List<PasteEntity> publicPastes = pastes.stream()
.filter(p -> !Boolean.TRUE.equals(p.getIs_private()))
.toList();
return Future.succeededFuture(publicPastes);
});
}
public Future<List<PasteEntity>> getAll(QueryParams params) {
return pasteDAO.getAll(params).compose(pastes -> {
List<PasteEntity> publicPastes = pastes.stream()
.filter(p -> !Boolean.TRUE.equals(p.getIs_private()))
.toList();
return Future.succeededFuture(publicPastes);
});
}
public Future<PasteEntity> getById(Long id) {
return pasteDAO.getById(id);
}
public Future<PasteEntity> getByKey(String key, String password) {
return pasteDAO.getByKey(key).compose(paste -> {
if (paste == null) {
return Future.failedFuture(new NotFoundException("Paste with key " + key));
}
if (Boolean.TRUE.equals(paste.getIs_private())) {
if (password == null || !PasswordHasher.verify(password, paste.getPassword())) {
return Future.failedFuture(new UnauthorizedException("Contraseña incorrecta"));
}
}
if (Boolean.TRUE.equals(paste.getBurn_after())) {
Vertx.vertx().setTimer(5000, _ -> {
pasteDAO.delete(paste.getPaste_id());
});
}
return Future.succeededFuture(paste);
});
}
public Future<PasteEntity> create(PasteEntity paste) {
return pasteValidator.validate(paste).compose(validation -> {
if (!validation.isValid()) {
return Future.failedFuture(new ValidationException(Constants.GSON.toJson(validation.getErrors())));
}
String key = PasteKeyGenerator.generate(6);
paste.setPaste_key(key);
if (paste.getPassword() != null) {
paste.setPassword(PasswordHasher.hash(paste.getPassword()));
}
return pasteDAO.existsByKey(key).compose(exists -> {
if (exists) {
return create(paste); // recursivo, genera otra clave
}
return pasteDAO.insert(paste);
});
});
}
public Future<PasteEntity> update(PasteEntity paste) { // nope
return pasteDAO.update(paste);
}
public Future<Boolean> delete(Long id) {
return getById(id).compose(paste -> {
if (paste == null) {
return Future.failedFuture(new NotFoundException("Paste with id " + id));
}
return pasteDAO.delete(id);
});
}
public Future<Boolean> exists(String key) {
return pasteDAO.existsByKey(key);
}
}

View File

@@ -0,0 +1,31 @@
package net.miarma.api.microservices.mpaste.validators;
import io.vertx.core.Future;
import net.miarma.api.backlib.validation.ValidationResult;
import net.miarma.api.microservices.mpaste.entities.PasteEntity;
public class PasteValidator {
public Future<ValidationResult> validate(PasteEntity entity) {
ValidationResult result = new ValidationResult();
if (entity.getTitle() == null || entity.getTitle().trim().isEmpty()) {
result.addError("title", "El título no puede estar vacío");
}
if (entity.getContent() == null || entity.getContent().trim().isEmpty()) {
result.addError("content", "El contenido no puede estar vacío");
}
if (Boolean.TRUE.equals(entity.getIs_private())) {
if (entity.getPassword() == null || entity.getPassword().trim().isEmpty()) {
result.addError("password", "Las pastes privadas requieren contraseña");
}
}
if (entity.getTitle() != null && entity.getTitle().length() > 128) {
result.addError("title", "Título demasiado largo (128 caracteres máx.)");
}
return Future.succeededFuture(result);
}
}

View File

@@ -0,0 +1,55 @@
package net.miarma.api.microservices.mpaste.verticles;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.microservices.mpaste.routing.MPasteDataRouter;
import net.miarma.api.microservices.mpaste.services.PasteService;
import net.miarma.api.backlib.util.EventBusUtil;
import net.miarma.api.backlib.util.RouterUtil;
public class MPasteDataVerticle extends AbstractVerticle {
private ConfigManager configManager;
private PasteService pasteService;
@Override
public void start(Promise<Void> startPromise) {
configManager = ConfigManager.getInstance();
Pool pool = DatabaseProvider.createPool(vertx, configManager);
pasteService = new PasteService(pool);
Router router = Router.router(vertx);
RouterUtil.attachLogger(router);
MPasteDataRouter.mount(router, vertx, pool);
registerLogicVerticleConsumer();
vertx.createHttpServer()
.requestHandler(router)
.listen(configManager.getIntProperty("mpaste.data.port"), res -> {
if (res.succeeded()) startPromise.complete();
else startPromise.fail(res.cause());
});
}
private void registerLogicVerticleConsumer() {
vertx.eventBus().consumer(Constants.MPASTE_EVENT_BUS, message -> {
JsonObject body = (JsonObject) message.body();
String action = body.getString("action");
switch (action) {
case "getByKey" -> {
pasteService.getByKey(body.getString("key"), body.getString("password"))
.onSuccess(paste -> message.reply(new JsonObject(Constants.GSON.toJson(paste))))
.onFailure(EventBusUtil.fail(message));
}
}
});
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.api.microservices.mpaste.verticles;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.ext.web.Router;
import io.vertx.sqlclient.Pool;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.db.DatabaseProvider;
import net.miarma.api.microservices.mpaste.routing.MPasteLogicRouter;
import net.miarma.api.backlib.util.RouterUtil;
public class MPasteLogicVerticle extends AbstractVerticle {
private ConfigManager configManager;
@Override
public void start(Promise<Void> startPromise) {
configManager = ConfigManager.getInstance();
Pool pool = DatabaseProvider.createPool(vertx, configManager);
Router router = Router.router(vertx);
RouterUtil.attachLogger(router);
MPasteLogicRouter.mount(router, vertx, pool);
vertx.createHttpServer()
.requestHandler(router)
.listen(configManager.getIntProperty("mpaste.logic.port"), res -> {
if (res.succeeded()) startPromise.complete();
else startPromise.fail(res.cause());
});
}
}

View File

@@ -0,0 +1,58 @@
package net.miarma.api.microservices.mpaste.verticles;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import net.miarma.api.backlib.ConfigManager;
import net.miarma.api.backlib.Constants;
import net.miarma.api.backlib.LogAccumulator;
import net.miarma.api.backlib.util.DeploymentUtil;
public class MPasteMainVerticle extends AbstractVerticle {
private ConfigManager configManager;
@Override
public void start(Promise<Void> startPromise) {
try {
this.configManager = ConfigManager.getInstance();
deployVerticles();
startPromise.complete();
} catch (Exception e) {
Constants.LOGGER.error(DeploymentUtil.failMessage(MPasteMainVerticle.class, e));
startPromise.fail(e);
}
}
private void deployVerticles() {
vertx.deployVerticle(new MPasteDataVerticle(), result -> {
if (result.succeeded()) {
String message = String.join("\n\r ",
DeploymentUtil.successMessage(MPasteDataVerticle.class),
DeploymentUtil.apiUrlMessage(
configManager.getHost(),
configManager.getIntProperty("mpaste.data.port")
)
);
LogAccumulator.add(message);
} else {
LogAccumulator.add(DeploymentUtil.failMessage(MPasteDataVerticle.class, result.cause()));
}
});
vertx.deployVerticle(new MPasteLogicVerticle(), result -> {
if (result.succeeded()) {
String message = String.join("\n\r ",
DeploymentUtil.successMessage(MPasteLogicVerticle.class),
DeploymentUtil.apiUrlMessage(
configManager.getHost(),
configManager.getIntProperty("mpaste.logic.port")
)
);
LogAccumulator.add(message);
} else {
LogAccumulator.add(DeploymentUtil.failMessage(MPasteLogicVerticle.class, result.cause()));
}
});
}
}