diff --git a/.classpath b/.classpath deleted file mode 100644 index f7e4a1d..0000000 --- a/.classpath +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.gitignore b/.gitignore index 9154f4c..2d249f9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ hs_err_pid* replay_pid* +.idea diff --git a/.project b/.project deleted file mode 100644 index 73e9ed9..0000000 --- a/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - backend - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 2f5cc74..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/README.md b/README.md index 4cb086e..3485b9a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # miarma-backend -Backend that powers all my services. \ No newline at end of file +Backend that provides auth and management for my self-hosted microservices \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..ef89f69 --- /dev/null +++ b/TODO @@ -0,0 +1,18 @@ +POR HACER -------------------------------- +- implementar urlParams para filtros +- documentación +- mail wrapper + +RESUELTO --------------------------------- +- añadir colaborador desde perfil +- apuntarse lista espera +- aceptar solicitudes LE/Colab (sobre todo por crear preusers) +- mejorar queries para no filtrar en memoria -> IMPOSIBLE CON ENDPOINTS INTERNOS DE CORE: RESUELTO CON CACHING +- normalizar el uso de services y repositories desde otros services y repositories +- sistema comun de errores en back & front +- nombre del requester +- cambiar contraseña (?) +- todos los socios en dropdown ingresos +- validacion LE/COlab +- createdAt custom en ing,gastos +- editar createdAt \ No newline at end of file diff --git a/backend.iml b/backend.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/backend.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/backlib/backlib.iml b/backlib/backlib.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/backlib/backlib.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/backlib/pom.xml b/backlib/pom.xml new file mode 100644 index 0000000..67601bd --- /dev/null +++ b/backlib/pom.xml @@ -0,0 +1,91 @@ + + 4.0.0 + backlib + net.miarma + 1.1.1 + + + 25 + 4.0.1 + 25 + 25 + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + MiarmaGit + https://git.miarma.net/api/packages/Gallardo7761/maven + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.mariadb.jdbc + mariadb-java-client + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/backlib/config/SecurityCommonConfig.java b/backlib/src/main/java/net/miarma/backlib/config/SecurityCommonConfig.java new file mode 100644 index 0000000..6bc3a67 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/config/SecurityCommonConfig.java @@ -0,0 +1,29 @@ +package net.miarma.backlib.config; + +import net.miarma.backlib.security.JwtService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class SecurityCommonConfig { + @Bean + public AuthenticationManager authManager(HttpSecurity http) throws Exception { + return http.getSharedObject(AuthenticationManagerBuilder.class) + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(12); + } + + @Bean + public JwtService jwtService() { + return new JwtService(); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/ApiErrorDto.java b/backlib/src/main/java/net/miarma/backlib/dto/ApiErrorDto.java new file mode 100644 index 0000000..ed05156 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/ApiErrorDto.java @@ -0,0 +1,65 @@ +package net.miarma.backlib.dto; + +import tools.jackson.databind.ObjectMapper; + +import java.time.Instant; + +public class ApiErrorDto { + private int status; + private String error; + private String message; + private String path; + private Instant timestamp; + + public ApiErrorDto(int status, String error, String message, String path) { + this.status = status; + this.error = error; + this.message = message; + this.path = path; + this.timestamp = Instant.now(); + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public String toJson() { + return new ObjectMapper().writeValueAsString(this); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/ApiValidationErrorDto.java b/backlib/src/main/java/net/miarma/backlib/dto/ApiValidationErrorDto.java new file mode 100644 index 0000000..868d8e4 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/ApiValidationErrorDto.java @@ -0,0 +1,57 @@ +package net.miarma.backlib.dto; + +import tools.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +public class ApiValidationErrorDto { + private int status; + private Map errors; + private String path; + private Instant timestamp; + + public ApiValidationErrorDto(Map errors, String path) { + this.status = 422; + this.errors = errors; + this.path = path; + this.timestamp = Instant.now(); + } + + public Map getErrors() { + return errors; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public void setErrors(Map errors) { + this.errors = errors; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public String toJson() { + return new ObjectMapper().writeValueAsString(this); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/ChangeAvatarRequest.java b/backlib/src/main/java/net/miarma/backlib/dto/ChangeAvatarRequest.java new file mode 100644 index 0000000..b1d1f61 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/ChangeAvatarRequest.java @@ -0,0 +1,3 @@ +package net.miarma.backlib.dto; + +public record ChangeAvatarRequest(String avatar) {} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/ChangePasswordRequest.java b/backlib/src/main/java/net/miarma/backlib/dto/ChangePasswordRequest.java new file mode 100644 index 0000000..8b30986 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/ChangePasswordRequest.java @@ -0,0 +1,10 @@ +package net.miarma.backlib.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record ChangePasswordRequest(@NotBlank String oldPassword, + @NotBlank String newPassword, + @NotBlank Byte serviceId) {} + diff --git a/backlib/src/main/java/net/miarma/backlib/dto/ChangeRoleRequest.java b/backlib/src/main/java/net/miarma/backlib/dto/ChangeRoleRequest.java new file mode 100644 index 0000000..52115da --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/ChangeRoleRequest.java @@ -0,0 +1,4 @@ +package net.miarma.backlib.dto; + +public record ChangeRoleRequest(Byte role) { +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/ChangeStatusRequest.java b/backlib/src/main/java/net/miarma/backlib/dto/ChangeStatusRequest.java new file mode 100644 index 0000000..bc667cd --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/ChangeStatusRequest.java @@ -0,0 +1,3 @@ +package net.miarma.backlib.dto; + +public record ChangeStatusRequest(Byte status) {} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/CreateCredentialDto.java b/backlib/src/main/java/net/miarma/backlib/dto/CreateCredentialDto.java new file mode 100644 index 0000000..b289dc0 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/CreateCredentialDto.java @@ -0,0 +1,56 @@ +package net.miarma.backlib.dto; + +import java.time.Instant; +import java.util.UUID; + +public class CreateCredentialDto { + private UUID userId; + private Byte serviceId; + private String username; + private String email; + private String password; + private Byte status; + + public CreateCredentialDto() {} + + public CreateCredentialDto(UUID userId, Byte serviceId, String username, String email, + String password, Byte status) { + this.userId = userId; + this.serviceId = serviceId; + this.username = username; + this.email = email; + this.password = password; + this.status = status; + } + + // Getters y setters + public UUID getUserId() { + return userId; + } + public void setUserId(UUID userId) { + this.userId = userId; + } + public Byte getServiceId() { + return serviceId; + } + public void setServiceId(Byte serviceId) { + this.serviceId = serviceId; + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + public String getEmail() { + return email; + } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public Byte getStatus() { + return status; + } + public void setStatus(Byte status) { + this.status = status; + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/CreateUserDto.java b/backlib/src/main/java/net/miarma/backlib/dto/CreateUserDto.java new file mode 100644 index 0000000..4b07f3d --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/CreateUserDto.java @@ -0,0 +1,6 @@ +package net.miarma.backlib.dto; + +public record CreateUserDto(String displayName, + String avatar) { + +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/CredentialDto.java b/backlib/src/main/java/net/miarma/backlib/dto/CredentialDto.java new file mode 100644 index 0000000..b68547f --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/CredentialDto.java @@ -0,0 +1,79 @@ +package net.miarma.backlib.dto; + +import java.time.Instant; +import java.util.UUID; + +public class CredentialDto { + private UUID credentialId; + private UUID userId; + private Byte serviceId; + private String username; + private String email; + private Byte status; + private Instant createdAt; + private Instant updatedAt; + + public CredentialDto() {} + + public CredentialDto(UUID credentialId, UUID userId, Byte serviceId, String username, String email, + Byte status, Instant createdAt, Instant updatedAt) { + this.credentialId = credentialId; + this.userId = userId; + this.serviceId = serviceId; + this.username = username; + this.email = email; + this.status = status; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + // Getters y setters + 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 Byte getServiceId() { + return serviceId; + } + public void setServiceId(Byte 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 Byte getStatus() { + return status; + } + public void setStatus(Byte status) { + this.status = status; + } + public Instant getCreatedAt() { + return createdAt; + } + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + public Instant getUpdatedAt() { + return updatedAt; + } + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/FileDto.java b/backlib/src/main/java/net/miarma/backlib/dto/FileDto.java new file mode 100644 index 0000000..3ec1473 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/FileDto.java @@ -0,0 +1,123 @@ +package net.miarma.backlib.dto; + +import java.time.Instant; +import java.util.UUID; + +public class FileDto { + + private FileDto() {} + + public static class Request { + private String fileName; + private String filePath; + private String mimeType; + private Byte context; + private UUID uploadedBy; + + 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 Byte getContext() { + return context; + } + + public void setContext(Byte context) { + this.context = context; + } + + public UUID getUploadedBy() { + return uploadedBy; + } + + public void setUploadedBy(UUID uploadedBy) { + this.uploadedBy = uploadedBy; + } + } + + public static class Response { + private UUID fileId; + private String fileName; + private String filePath; + private String mimeType; + private UUID uploadedBy; + private Instant uploadedAt; + private Byte context; + + 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 Instant getUploadedAt() { + return uploadedAt; + } + + public void setUploadedAt(Instant uploadedAt) { + this.uploadedAt = uploadedAt; + } + + public Byte getContext() { + return context; + } + + public void setContext(Byte context) { + this.context = context; + } + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/LoginRequest.java b/backlib/src/main/java/net/miarma/backlib/dto/LoginRequest.java new file mode 100644 index 0000000..e736bfa --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/LoginRequest.java @@ -0,0 +1,8 @@ +package net.miarma.backlib.dto; + +import jakarta.validation.constraints.NotBlank; +import tools.jackson.databind.JsonNode; + +public record LoginRequest(@NotBlank String username, + @NotBlank String password, + Byte serviceId) {} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/LoginResponse.java b/backlib/src/main/java/net/miarma/backlib/dto/LoginResponse.java new file mode 100644 index 0000000..068831c --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/LoginResponse.java @@ -0,0 +1,7 @@ +package net.miarma.backlib.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record LoginResponse(@JsonProperty("token") String token, + UserDto user, + CredentialDto account) {} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/backlib/dto/RegisterRequest.java b/backlib/src/main/java/net/miarma/backlib/dto/RegisterRequest.java new file mode 100644 index 0000000..fc447fd --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/RegisterRequest.java @@ -0,0 +1,9 @@ +package net.miarma.backlib.dto; + +import jakarta.validation.constraints.NotBlank; + +public record RegisterRequest(String displayName, + @NotBlank String username, + @NotBlank String email, + @NotBlank String password, + Byte serviceId) {} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/backlib/dto/UserDto.java b/backlib/src/main/java/net/miarma/backlib/dto/UserDto.java new file mode 100644 index 0000000..ac17abc --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/UserDto.java @@ -0,0 +1,71 @@ +package net.miarma.backlib.dto; + +import java.time.Instant; +import java.util.UUID; + +public class UserDto { + private UUID userId; + private String displayName; + private String avatar; + private Byte globalStatus; + private Byte globalRole; + private Instant createdAt; + private Instant updatedAt; + + public UserDto() {} + + public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole, + Instant createdAt, Instant updatedAt) { + this.userId = userId; + this.displayName = displayName; + this.avatar = avatar; + this.globalStatus = globalStatus; + this.globalRole = globalRole; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + // Getters y setters + public UUID getUserId() { + return userId; + } + public void setUserId(UUID userId) { + this.userId = userId; + } + 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 Byte getGlobalStatus() { + return globalStatus; + } + public void setGlobalStatus(Byte globalStatus) { + this.globalStatus = globalStatus; + } + public Byte getGlobalRole() { + return globalRole; + } + public void setGlobalRole(Byte globalRole) { + this.globalRole = globalRole; + } + public Instant getCreatedAt() { + return createdAt; + } + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + public Instant getUpdatedAt() { + return updatedAt; + } + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/UserExistsResponse.java b/backlib/src/main/java/net/miarma/backlib/dto/UserExistsResponse.java new file mode 100644 index 0000000..f767ee4 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/UserExistsResponse.java @@ -0,0 +1,4 @@ +package net.miarma.backlib.dto; + +public record UserExistsResponse(boolean exists) { +} diff --git a/backlib/src/main/java/net/miarma/backlib/dto/UserWithCredentialDto.java b/backlib/src/main/java/net/miarma/backlib/dto/UserWithCredentialDto.java new file mode 100644 index 0000000..c62d547 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/dto/UserWithCredentialDto.java @@ -0,0 +1,3 @@ +package net.miarma.backlib.dto; + +public record UserWithCredentialDto(UserDto user, CredentialDto account) {} diff --git a/backlib/src/main/java/net/miarma/backlib/exception/BadRequestException.java b/backlib/src/main/java/net/miarma/backlib/exception/BadRequestException.java new file mode 100644 index 0000000..60c0621 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package net.miarma.backlib.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/exception/ConflictException.java b/backlib/src/main/java/net/miarma/backlib/exception/ConflictException.java new file mode 100644 index 0000000..4cfe49a --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/exception/ConflictException.java @@ -0,0 +1,7 @@ +package net.miarma.backlib.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/exception/ForbiddenException.java b/backlib/src/main/java/net/miarma/backlib/exception/ForbiddenException.java new file mode 100644 index 0000000..64e03bf --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package net.miarma.backlib.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/exception/NotFoundException.java b/backlib/src/main/java/net/miarma/backlib/exception/NotFoundException.java new file mode 100644 index 0000000..2f8892c --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/exception/NotFoundException.java @@ -0,0 +1,5 @@ +package net.miarma.backlib.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { super(message); } +} diff --git a/backlib/src/main/java/net/miarma/backlib/exception/UnauthorizedException.java b/backlib/src/main/java/net/miarma/backlib/exception/UnauthorizedException.java new file mode 100644 index 0000000..39c35b3 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/exception/UnauthorizedException.java @@ -0,0 +1,7 @@ +package net.miarma.backlib.exception; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/exception/ValidationException.java b/backlib/src/main/java/net/miarma/backlib/exception/ValidationException.java new file mode 100644 index 0000000..0a0741d --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/exception/ValidationException.java @@ -0,0 +1,21 @@ +package net.miarma.backlib.exception; + +public class ValidationException extends RuntimeException { + private final String field; + private final String message; + + public ValidationException(String field, String message) { + super(message); + this.field = field; + this.message = message; + } + + public String getField() { + return field; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/filter/RequestLoggingFilter.java b/backlib/src/main/java/net/miarma/backlib/filter/RequestLoggingFilter.java new file mode 100644 index 0000000..e5fc004 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/filter/RequestLoggingFilter.java @@ -0,0 +1,41 @@ +package net.miarma.backlib.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class RequestLoggingFilter extends OncePerRequestFilter { + + private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class); + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + long start = System.currentTimeMillis(); + + filterChain.doFilter(request, response); + + long duration = System.currentTimeMillis() - start; + + log.info("({}) {} {} -> {} ({} ms)", + request.getRemoteAddr(), + request.getMethod(), + request.getRequestURI(), + response.getStatus(), + duration + ); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/http/DevCorsConfig.java b/backlib/src/main/java/net/miarma/backlib/http/DevCorsConfig.java new file mode 100644 index 0000000..f31057e --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/http/DevCorsConfig.java @@ -0,0 +1,27 @@ +package net.miarma.backlib.http; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@Profile("dev") // esto asegura que solo se cargue en dev +public class DevCorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:3000") // tu frontend React + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowCredentials(true) + .allowedHeaders("*"); + } + }; + } +} + diff --git a/backlib/src/main/java/net/miarma/backlib/http/GlobalExceptionHandler.java b/backlib/src/main/java/net/miarma/backlib/http/GlobalExceptionHandler.java new file mode 100644 index 0000000..1b9c84e --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/http/GlobalExceptionHandler.java @@ -0,0 +1,106 @@ +package net.miarma.backlib.http; + +import jakarta.servlet.http.HttpServletRequest; +import net.miarma.backlib.dto.ApiErrorDto; +import net.miarma.backlib.dto.ApiValidationErrorDto; +import net.miarma.backlib.exception.*; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFound( + NotFoundException ex, HttpServletRequest req) { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.NOT_FOUND.value(), + HttpStatus.NOT_FOUND.getReasonPhrase(), + ex.getMessage(), + req.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequest( + BadRequestException ex, HttpServletRequest req) { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.BAD_REQUEST.value(), + HttpStatus.BAD_REQUEST.getReasonPhrase(), + ex.getMessage(), + req.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorized( + UnauthorizedException ex, HttpServletRequest req) { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.UNAUTHORIZED.value(), + HttpStatus.UNAUTHORIZED.getReasonPhrase(), + ex.getMessage(), + req.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbidden( + ForbiddenException ex, HttpServletRequest req) { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.FORBIDDEN.value(), + HttpStatus.FORBIDDEN.getReasonPhrase(), + ex.getMessage(), + req.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); + } + + @ExceptionHandler(ConflictException.class) + public ResponseEntity handleConflict( + ConflictException ex, HttpServletRequest req) { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.CONFLICT.value(), + HttpStatus.CONFLICT.getReasonPhrase(), + ex.getMessage(), + req.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + + @ExceptionHandler(ValidationException.class) + public ResponseEntity handleValidation( + ValidationException ex, HttpServletRequest req) { + Map errors = Map.of(ex.getField(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.UNPROCESSABLE_CONTENT).body(new ApiValidationErrorDto(errors, req.getRequestURI())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAll( + Exception ex, HttpServletRequest req) { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.INTERNAL_SERVER_ERROR.value(), + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), + "Internal server error", + req.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/http/RestAccessDeniedHandler.java b/backlib/src/main/java/net/miarma/backlib/http/RestAccessDeniedHandler.java new file mode 100644 index 0000000..2a54995 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/http/RestAccessDeniedHandler.java @@ -0,0 +1,31 @@ +package net.miarma.backlib.http; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.miarma.backlib.dto.ApiErrorDto; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RestAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.FORBIDDEN.value(), + HttpStatus.FORBIDDEN.getReasonPhrase(), + "Forbidden", + request.getRequestURI() + ); + + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.setContentType("application/json"); + response.getWriter().write(error.toJson()); + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/backlib/http/RestAuthEntryPoint.java b/backlib/src/main/java/net/miarma/backlib/http/RestAuthEntryPoint.java new file mode 100644 index 0000000..e2e2f68 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/http/RestAuthEntryPoint.java @@ -0,0 +1,34 @@ +package net.miarma.backlib.http; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.miarma.backlib.dto.ApiErrorDto; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RestAuthEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + + ApiErrorDto error = new ApiErrorDto( + HttpStatus.UNAUTHORIZED.value(), + HttpStatus.UNAUTHORIZED.getReasonPhrase(), + "Unauthorized", + request.getRequestURI() + ); + + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType("application/json"); + response.getWriter().write(error.toJson()); + } +} + diff --git a/backlib/src/main/java/net/miarma/backlib/security/CoreAuthTokenHolder.java b/backlib/src/main/java/net/miarma/backlib/security/CoreAuthTokenHolder.java new file mode 100644 index 0000000..60b760f --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/security/CoreAuthTokenHolder.java @@ -0,0 +1,24 @@ +package net.miarma.backlib.security; + +import org.springframework.stereotype.Component; + +import java.time.Instant; + +@Component +public class CoreAuthTokenHolder { + private volatile String token; + private volatile Instant expiresAt; + + public String getToken() { + return token; + } + + public boolean isExpired() { + return expiresAt == null || Instant.now().isAfter(expiresAt.minusSeconds(30)); + } + + public void setToken(String token, Instant expiresAt) { + this.token = token; + this.expiresAt = expiresAt; + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/security/JwtService.java b/backlib/src/main/java/net/miarma/backlib/security/JwtService.java new file mode 100644 index 0000000..5a0b1f1 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/security/JwtService.java @@ -0,0 +1,102 @@ +package net.miarma.backlib.security; + +import io.jsonwebtoken.*; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Service +public class JwtService { + + @Value("${jwt.private-key-path}") + private String privateKeyPath; + + @Value("${jwt.public-key-path}") + private String publicKeyPath; + + @Value("${jwt.expiration-ms}") + private long expiration; + + private PrivateKey privateKey; + private PublicKey publicKey; + + @PostConstruct + public void init() throws Exception { + this.privateKey = loadPrivateKey(privateKeyPath); + this.publicKey = loadPublicKey(publicKeyPath); + } + + private PrivateKey loadPrivateKey(String path) throws Exception { + String pem = Files.readString(Path.of(path)); + pem = pem.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + byte[] decoded = Base64.getDecoder().decode(pem); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(spec); + } + + private PublicKey loadPublicKey(String path) throws Exception { + String pem = Files.readString(Path.of(path)); + pem = pem.replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + + byte[] decoded = Base64.getDecoder().decode(pem); + X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic(spec); + } + + public String generateToken(UUID userId, Byte serviceId) { + Date now = new Date(); + Date exp = new Date(now.getTime() + expiration); + + return Jwts.builder() + .setSubject(userId.toString()) + .claim("service", serviceId) + .setIssuedAt(now) + .setExpiration(exp) + .signWith(privateKey, SignatureAlgorithm.RS256) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + public UUID getUserId(String token) { + Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody(); + return UUID.fromString(claims.getSubject()); + } + + public Byte getServiceId(String token) { + Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody(); + return ((Number) claims.get("service")).byteValue(); + } + + public Date getExpiration(String token) { + Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody(); + return claims.getExpiration(); + } +} + diff --git a/backlib/src/main/java/net/miarma/backlib/security/PasswordGenerator.java b/backlib/src/main/java/net/miarma/backlib/security/PasswordGenerator.java new file mode 100644 index 0000000..bddad75 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/security/PasswordGenerator.java @@ -0,0 +1,45 @@ +package net.miarma.backlib.security; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PasswordGenerator { + + private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String LOWER = "abcdefghijklmnopqrstuvwxyz"; + private static final String DIGITS = "0123456789"; + private static final String SYMBOLS = "!@#$%^&*"; // compatibles con bcrypt + private static final String ALL = UPPER + LOWER + DIGITS + SYMBOLS; + + private static final SecureRandom random = new SecureRandom(); + + public static String generate(int length) { + if (length < 8) length = 8; + + List password = new ArrayList<>(); + + password.add(getRandChar(UPPER)); + password.add(getRandChar(LOWER)); + password.add(getRandChar(DIGITS)); + password.add(getRandChar(SYMBOLS)); + + while (password.size() < length) { + password.add(getRandChar(ALL)); + } + + Collections.shuffle(password, random); + + StringBuilder sb = new StringBuilder(); + for (char c : password) { + sb.append(c); + } + + return sb.toString(); + } + + private static char getRandChar(String chars) { + return chars.charAt(random.nextInt(chars.length())); + } +} diff --git a/backlib/src/main/java/net/miarma/backlib/security/ServiceAuthFilter.java b/backlib/src/main/java/net/miarma/backlib/security/ServiceAuthFilter.java new file mode 100644 index 0000000..63e2ab3 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/security/ServiceAuthFilter.java @@ -0,0 +1,45 @@ +package net.miarma.backlib.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class ServiceAuthFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + + public ServiceAuthFilter(JwtService jwtService) { + this.jwtService = jwtService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String path = request.getRequestURI(); + + if (path.startsWith("/users/service")) { + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + String token = authHeader.substring(7); + if (!jwtService.validateToken(token)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/backlib/src/main/java/net/miarma/backlib/util/UuidUtil.java b/backlib/src/main/java/net/miarma/backlib/util/UuidUtil.java new file mode 100644 index 0000000..2d915f3 --- /dev/null +++ b/backlib/src/main/java/net/miarma/backlib/util/UuidUtil.java @@ -0,0 +1,21 @@ +package net.miarma.backlib.util; + +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UuidUtil { + public static byte[] uuidToBin(UUID uuid) { + ByteBuffer bb = ByteBuffer.allocate(16); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return bb.array(); + } + + public static UUID binToUUID(byte[] bin) { + ByteBuffer bb = ByteBuffer.wrap(bin); + long high = bb.getLong(); + long low = bb.getLong(); + return new UUID(high, low); + } + +} diff --git a/backlib/target/maven-archiver/pom.properties b/backlib/target/maven-archiver/pom.properties new file mode 100644 index 0000000..94832da --- /dev/null +++ b/backlib/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=backlib +groupId=net.miarma +version=1.1.0 diff --git a/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..bdbc1e3 --- /dev/null +++ b/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,36 @@ +net/miarma/backlib/dto/LoginResponse.class +net/miarma/backlib/dto/CreateUserDto.class +net/miarma/backlib/http/DevCorsConfig$1.class +net/miarma/backlib/dto/ApiErrorDto.class +net/miarma/backlib/dto/ChangeStatusRequest.class +net/miarma/backlib/exception/ConflictException.class +net/miarma/backlib/http/RestAuthEntryPoint.class +net/miarma/backlib/exception/NotFoundException.class +net/miarma/backlib/http/RestAccessDeniedHandler.class +net/miarma/backlib/dto/ChangeAvatarRequest.class +net/miarma/backlib/dto/FileDto$Response.class +net/miarma/backlib/dto/CredentialDto.class +net/miarma/backlib/util/UuidUtil.class +net/miarma/backlib/dto/FileDto$Request.class +net/miarma/backlib/dto/ApiValidationErrorDto.class +net/miarma/backlib/security/JwtService.class +net/miarma/backlib/dto/UserDto.class +net/miarma/backlib/dto/LoginRequest.class +net/miarma/backlib/exception/BadRequestException.class +net/miarma/backlib/security/CoreAuthTokenHolder.class +net/miarma/backlib/dto/CreateCredentialDto.class +net/miarma/backlib/dto/UserWithCredentialDto.class +net/miarma/backlib/dto/RegisterRequest.class +net/miarma/backlib/dto/ChangePasswordRequest.class +net/miarma/backlib/http/GlobalExceptionHandler.class +net/miarma/backlib/dto/UserExistsResponse.class +net/miarma/backlib/dto/ChangeRoleRequest.class +net/miarma/backlib/exception/ForbiddenException.class +net/miarma/backlib/exception/UnauthorizedException.class +net/miarma/backlib/dto/FileDto.class +net/miarma/backlib/security/PasswordGenerator.class +net/miarma/backlib/config/SecurityCommonConfig.class +net/miarma/backlib/filter/RequestLoggingFilter.class +net/miarma/backlib/exception/ValidationException.class +net/miarma/backlib/security/ServiceAuthFilter.class +net/miarma/backlib/http/DevCorsConfig.class diff --git a/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..30ddccf --- /dev/null +++ b/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,33 @@ +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/config/SecurityCommonConfig.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ApiErrorDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ApiValidationErrorDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeAvatarRequest.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangePasswordRequest.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeRoleRequest.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeStatusRequest.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CreateCredentialDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CreateUserDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CredentialDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/FileDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/LoginRequest.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/LoginResponse.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/RegisterRequest.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserExistsResponse.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserWithCredentialDto.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/BadRequestException.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ConflictException.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ForbiddenException.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/NotFoundException.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/UnauthorizedException.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ValidationException.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/filter/RequestLoggingFilter.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/DevCorsConfig.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/GlobalExceptionHandler.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/RestAccessDeniedHandler.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/RestAuthEntryPoint.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/CoreAuthTokenHolder.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/JwtService.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/PasswordGenerator.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/ServiceAuthFilter.java +/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/util/UuidUtil.java diff --git a/backlib/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/backlib/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/backlib/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/backlib/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/build-upload.sh b/build-upload.sh new file mode 100755 index 0000000..098d6d2 --- /dev/null +++ b/build-upload.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cd core/ +mvn clean package +cd .. +cd huertos/ +mvn clean package +cd .. +scp core/target/core-1.0.0.jar jomaa@10.0.0.254:/home/jomaa/transfer +scp huertos/target/huertos-1.0.0.jar jomaa@10.0.0.254:/home/jomaa/transfer diff --git a/cine/.classpath b/cine/.classpath deleted file mode 100644 index f7e4a1d..0000000 --- a/cine/.classpath +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cine/.project b/cine/.project deleted file mode 100644 index d49c5d8..0000000 --- a/cine/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - cine - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/cine/.settings/org.eclipse.jdt.core.prefs b/cine/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 2f5cc74..0000000 --- a/cine/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/cine/.settings/org.eclipse.m2e.core.prefs b/cine/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/cine/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/cine/cine.iml b/cine/cine.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/cine/cine.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/cine/pom.xml b/cine/pom.xml index 5928dbc..e33e655 100644 --- a/cine/pom.xml +++ b/cine/pom.xml @@ -9,6 +9,13 @@ cine + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + diff --git a/core/.classpath b/core/.classpath deleted file mode 100644 index f7e4a1d..0000000 --- a/core/.classpath +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/.project b/core/.project deleted file mode 100644 index 1a64507..0000000 --- a/core/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - core - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/core/.settings/org.eclipse.jdt.core.prefs b/core/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 2f5cc74..0000000 --- a/core/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/core/.settings/org.eclipse.m2e.core.prefs b/core/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/core/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/core/core.iml b/core/core.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/core/core.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index d3adb50..a68de1a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -9,6 +9,18 @@ core + + 25 + 25 + + + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + @@ -44,6 +56,10 @@ 1.18.42 compile + + org.springframework.boot + spring-boot-starter-webflux + @@ -63,6 +79,29 @@ 0.11.5 runtime + + + net.miarma + backlib + 1.1.0 + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/CoreApplication.java b/core/src/main/java/net/miarma/backend/core/CoreApplication.java index 272d01d..a328cb1 100644 --- a/core/src/main/java/net/miarma/backend/core/CoreApplication.java +++ b/core/src/main/java/net/miarma/backend/core/CoreApplication.java @@ -1,9 +1,14 @@ package net.miarma.backend.core; +import net.miarma.backlib.config.SecurityCommonConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; -@SpringBootApplication +@SpringBootApplication(scanBasePackages = { + "net.miarma.backend.core", + "net.miarma.backlib" +}) public class CoreApplication { public static void main(String[] args) { SpringApplication.run(CoreApplication.class, args); diff --git a/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java b/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java index da00bb9..a861f3d 100644 --- a/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java +++ b/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java @@ -1,31 +1,62 @@ package net.miarma.backend.core.config; +import net.miarma.backend.core.security.JwtFilter; +import net.miarma.backlib.http.RestAccessDeniedHandler; +import net.miarma.backlib.http.RestAuthEntryPoint; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Optional; @Configuration @EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { - private final JwtFilter jwtFilter; + private final JwtFilter jwtFilter; + private final RestAuthEntryPoint authEntryPoint; + private final RestAccessDeniedHandler accessDeniedHandler; + private final CorsConfigurationSource corsConfigurationSource; - public SecurityConfig(JwtFilter jwtFilter) { + public SecurityConfig( + JwtFilter jwtFilter, + RestAuthEntryPoint authEntryPoint, + RestAccessDeniedHandler accessDeniedHandler, + Optional corsConfigurationSource + ) { this.jwtFilter = jwtFilter; + this.authEntryPoint = authEntryPoint; + this.accessDeniedHandler = accessDeniedHandler; + this.corsConfigurationSource = corsConfigurationSource.orElse(null); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + if (corsConfigurationSource != null) { + http.cors(Customizer.withDefaults()); + } + http .csrf(csrf -> csrf.disable()) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(authEntryPoint) + .accessDeniedHandler(accessDeniedHandler) + ) .authorizeHttpRequests(auth -> auth - .requestMatchers("/auth/**").permitAll() + .requestMatchers("/auth/login").permitAll() + .requestMatchers("/auth/refresh").permitAll() + .requestMatchers("/auth/change-password").permitAll() + .requestMatchers("/auth/register").permitAll() + .requestMatchers("/test").permitAll() + .requestMatchers("/screenshot").permitAll() .anyRequest().authenticated() ); @@ -33,9 +64,4 @@ public class SecurityConfig { return http.build(); } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(12); - } } diff --git a/core/src/main/java/net/miarma/backend/core/config/WebClientConfig.java b/core/src/main/java/net/miarma/backend/core/config/WebClientConfig.java new file mode 100644 index 0000000..96fddf7 --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/config/WebClientConfig.java @@ -0,0 +1,13 @@ +package net.miarma.backend.core.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + @Bean + public WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } +} diff --git a/core/src/main/java/net/miarma/backend/core/controller/AuthController.java b/core/src/main/java/net/miarma/backend/core/controller/AuthController.java index 11aa3a1..fee78c5 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/AuthController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/AuthController.java @@ -1,60 +1,122 @@ package net.miarma.backend.core.controller; +import java.util.List; import java.util.Map; import java.util.UUID; +import net.miarma.backlib.dto.*; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; -import net.miarma.backend.core.dto.LoginRequest; -import net.miarma.backend.core.dto.LoginResponse; +import net.miarma.backend.core.model.Credential; import net.miarma.backend.core.service.AuthService; -import net.miarma.backend.core.service.JwtService; +import net.miarma.backend.core.service.CredentialService; +import net.miarma.backlib.security.JwtService; @RestController @RequestMapping("/auth") public class AuthController { - private final AuthService authService; private final JwtService jwtService; + private final AuthService authService; - public AuthController(AuthService authService, JwtService jwtService) { - this.authService = authService; + public AuthController(JwtService jwtService, AuthService authService) { this.jwtService = jwtService; + this.authService = authService; } @PostMapping("/login") public ResponseEntity login(@Valid @RequestBody LoginRequest request) { LoginResponse response = authService.login(request); - return ResponseEntity.ok(response); + return ResponseEntity.ok( + new LoginResponse(response.token(), response.user(), response.account()) + ); } - @PostMapping("/refresh") + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequest request) { + return ResponseEntity.ok(authService.register(request)); + } + + @GetMapping("/refresh") public ResponseEntity refreshToken(@RequestHeader("Authorization") String authHeader) { if (authHeader == null || !authHeader.startsWith("Bearer ")) { - return ResponseEntity.status(401).body("Token missing"); + return ResponseEntity.status(401).body( + new ApiErrorDto( + 401, + "Unauthorized", + "No hay token", + "/v2/core/auth/change-password" + ) + ); } String token = authHeader.substring(7); if (!jwtService.validateToken(token)) { - return ResponseEntity.status(401).body("Invalid token"); + return ResponseEntity.status(401).body( + new ApiErrorDto( + 401, + "Unauthorized", + "Invalid token", + "/v2/core/auth/change-password" + ) + ); } UUID userId = jwtService.getUserId(token); - short serviceId = jwtService.getServiceId(token); + Byte serviceId = jwtService.getServiceId(token); String newToken = jwtService.generateToken(userId, serviceId); return ResponseEntity.ok(Map.of( - "token", newToken, - "userId", userId, - "serviceId", serviceId + "token", newToken, + "userId", userId, + "serviceId", serviceId )); } + @PostMapping("/change-password") + public ResponseEntity changePassword( + @RequestHeader("Authorization") String authHeader, + @RequestBody ChangePasswordRequest request + ) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return ResponseEntity.status(401).body( + new ApiErrorDto( + 401, + "Unauthorized", + "No hay token", + "/v2/core/auth/change-password" + ) + ); + } + + String token = authHeader.substring(7); + if (!jwtService.validateToken(token)) { + return ResponseEntity.status(401).body( + new ApiErrorDto( + 401, + "Unauthorized", + "Invalid token", + "/v2/core/auth/change-password" + ) + ); + } + + UUID userId = jwtService.getUserId(token); + + authService.changePassword(userId, request); + return ResponseEntity.ok(Map.of("message", "Contraseña cambiada correctamente")); + } + + + @GetMapping("/validate") + public ResponseEntity validate(@RequestHeader("Authorization") String authHeader) { + String token = authHeader.substring(7); + return ResponseEntity.ok(jwtService.validateToken(token)); + } } diff --git a/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java b/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java index 96e56ee..fdb7129 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java @@ -1,5 +1,97 @@ package net.miarma.backend.core.controller; +import java.util.List; +import java.util.UUID; + +import net.miarma.backend.core.dto.UpdateCredentialDto; +import net.miarma.backend.core.mapper.CredentialMapper; +import net.miarma.backend.core.mapper.UserMapper; +import net.miarma.backend.core.security.CorePrincipal; +import net.miarma.backend.core.service.UserService; +import net.miarma.backlib.dto.*; +import net.miarma.backlib.exception.ForbiddenException; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import net.miarma.backend.core.model.Credential; +import net.miarma.backend.core.service.CredentialService; + +@RestController +@RequestMapping("/credentials") public class CredentialController { + private final CredentialService credentialService; + + public CredentialController(CredentialService credentialService) { + this.credentialService = credentialService; + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getAll() { + return ResponseEntity.ok(credentialService.getAll()); + } + + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity create(@RequestBody CreateCredentialDto dto) { + return ResponseEntity.ok( + CredentialMapper.toDto( + credentialService.create( + CredentialMapper.toEntity(dto))) + ); + } + + @GetMapping("/test/{userId}") + public ResponseEntity test(@PathVariable("userId") UUID userId) { + CorePrincipal principal = (CorePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return ResponseEntity.ok( + userId.equals(principal.getUserId()) ? "OK" : "NO" + ); + } + + @GetMapping("/user/{userId}") + public ResponseEntity> getByUserId(@PathVariable("userId") UUID userId) { + CorePrincipal principal = (CorePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (!principal.getRole().equals((byte)1) && !principal.getUserId().equals(userId)) { + throw new ForbiddenException("No tienes permiso"); + } + return ResponseEntity.ok(credentialService.getByUserId(userId).stream() + .map(CredentialMapper::toDto) + .toList()); + } + + @GetMapping("/{credential_id}") + public ResponseEntity getById(@PathVariable("credential_id") UUID credentialId) { + CorePrincipal principal = (CorePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + Credential cred = credentialService.getById(credentialId); + if (!principal.getRole().equals((byte)1) && !cred.getUserId().equals(principal.getUserId())) { + throw new ForbiddenException("No tienes permiso"); + } + return ResponseEntity.ok(CredentialMapper.toDto(cred)); + } + + @PutMapping("/{credential_id}") + public ResponseEntity update( + @PathVariable("credential_id") UUID credentialId, + @RequestBody UpdateCredentialDto dto + ) { + CorePrincipal principal = (CorePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + Credential cred = credentialService.getById(credentialId); + if (!principal.getRole().equals((byte)1) && !cred.getUserId().equals(principal.getUserId())) { + throw new ForbiddenException("No tienes permiso"); + } + return ResponseEntity.ok( + CredentialMapper.toDto( + credentialService.update(credentialId, CredentialMapper.toEntity(dto)) + ) + ); + } + + @GetMapping("/{service_id}/{user_id}/status") + public ResponseEntity getStatus(@PathVariable("user_id") UUID userId, @PathVariable("service_id") Byte serviceId) { + return ResponseEntity.ok(credentialService.getStatus(userId, serviceId)); + } } diff --git a/core/src/main/java/net/miarma/backend/core/controller/FileController.java b/core/src/main/java/net/miarma/backend/core/controller/FileController.java index d2dea16..212a5a0 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/FileController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/FileController.java @@ -1,5 +1,90 @@ package net.miarma.backend.core.controller; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import net.miarma.backend.core.mapper.FileMapper; +import net.miarma.backlib.dto.FileDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import net.miarma.backend.core.model.File; +import net.miarma.backend.core.service.FileService; + +@RestController +@RequestMapping("/files") public class FileController { + private final FileService fileService; + + public FileController(FileService fileService) { + this.fileService = fileService; + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getAll() { + List files = fileService.getAll(); + return ResponseEntity.ok(files); + } + + @GetMapping("/{file_id}") + @PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#file_id, authentication.principal.userId)") + public ResponseEntity getById(@PathVariable("file_id") UUID fileId) { + File file = fileService.getById(fileId); + return ResponseEntity.ok(file); + } + + @PostMapping(consumes = "multipart/form-data") + @PreAuthorize("hasRole('ADMIN') or #uploadedBy == authentication.principal.userId") + public ResponseEntity create( + @RequestPart("file") MultipartFile file, + @RequestPart("fileName") String fileName, + @RequestPart("mimeType") String mimeType, + @RequestPart("uploadedBy") UUID uploadedBy, + @RequestPart("context") Integer context + ) throws IOException { + + File entity = new File(); + entity.setFileName(fileName); + entity.setMimeType(mimeType); + entity.setUploadedBy(uploadedBy); + entity.setContext(context.byteValue()); + + File created = fileService.create(entity, file.getBytes()); + return ResponseEntity.status(HttpStatus.CREATED) + .body(FileMapper.toResponse(created)); + } + + @PutMapping("/{file_id}") + @PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#file_id, authentication.principal.userId)") + public ResponseEntity update(@PathVariable("file_id") UUID fileId, @RequestBody FileDto.Request request) { + File updated = fileService.update(fileId, FileMapper.toEntity(request)); + return ResponseEntity.ok(updated); + } + + @DeleteMapping("/{file_id}") + @PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#file_id, authentication.principal.userId)") + public ResponseEntity delete(@PathVariable("file_id") UUID fileId, @RequestBody Map body) throws IOException { + String filePath = body.get("file_path"); + Files.deleteIfExists(Paths.get(filePath)); + fileService.delete(fileId); + return ResponseEntity.ok().build(); + } } diff --git a/core/src/main/java/net/miarma/backend/core/controller/UserController.java b/core/src/main/java/net/miarma/backend/core/controller/UserController.java index aa465e9..832be1c 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/UserController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/UserController.java @@ -1,10 +1,174 @@ package net.miarma.backend.core.controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.UUID; + +import net.miarma.backend.core.mapper.CredentialMapper; +import net.miarma.backend.core.model.Credential; +import net.miarma.backend.core.service.CredentialService; +import net.miarma.backlib.dto.*; +import org.apache.coyote.Response; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import net.miarma.backend.core.mapper.UserMapper; +import net.miarma.backend.core.model.User; +import net.miarma.backlib.security.JwtService; +import net.miarma.backend.core.service.UserService; @RestController @RequestMapping("/users") public class UserController { + private UserService userService; + private CredentialService credentialService; + private JwtService jwtService; + public UserController(UserService userService, CredentialService credentialService, JwtService jwtService) { + this.userService = userService; + this.credentialService = credentialService; + this.jwtService = jwtService; + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getAll() { + return ResponseEntity.ok( + userService.getAll() + .stream() + .map(UserMapper::toDto) + .toList() + ); + } + + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity create(@RequestBody CreateUserDto dto) { + return ResponseEntity.ok( + UserMapper.toDto( + userService.create( + UserMapper.fromCreateDto(dto))) + ); + } + + @GetMapping("/{user_id}") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity getById(@PathVariable("user_id") UUID userId) { + User user = userService.getById(userId); + return ResponseEntity.ok(UserMapper.toDto(user)); + } + + @GetMapping("/service/{service_id}") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getAllWithCredentials( + @PathVariable("service_id") Byte serviceId + ) { + List credentials = credentialService.getByServiceIdFetchUser(serviceId); + + List result = credentials.stream() + .map(cred -> new UserWithCredentialDto( + UserMapper.toDto(cred.getUser()), + CredentialMapper.toDto(cred) + )) + .toList(); + + return ResponseEntity.ok(result); + } + + @GetMapping("/{user_id}/service/{service_id}") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity getByIdWithCredentials( + @PathVariable("user_id") UUID userId, + @PathVariable("service_id") Byte serviceId + ) { + User user = userService.getById(userId); + Credential credential = credentialService.getByUserIdAndService(userId, serviceId); + return ResponseEntity.ok( + UserMapper.toDtoWithCredentials( + UserMapper.toDto(user), + CredentialMapper.toDto(credential) + ) + ); + } + + @PutMapping("/{user_id}") + @PreAuthorize("hasRole('ADMIN') or #userId == principal.userId") + public ResponseEntity update( + @PathVariable("user_id") UUID userId, + @RequestBody UserDto dto + ) { + User updated = userService.update(userId, UserMapper.fromDto(dto)); + return ResponseEntity.ok(UserMapper.toDto(updated)); + } + + @GetMapping("/{user_id}/avatar") + public ResponseEntity getAvatar(@PathVariable("user_id") UUID userId) { + return ResponseEntity.ok(userService.getById(userId).getAvatar()); + } + + @PutMapping("/{user_id}/avatar") + public ResponseEntity updateAvatar(@PathVariable("user_id") UUID userId, @RequestBody ChangeAvatarRequest avatar) { + return ResponseEntity.ok(userService.updateAvatar(userId, avatar)); + } + + @GetMapping("/{user_id}/status") + public ResponseEntity getStatus(@PathVariable("user_id") UUID userId) { + return ResponseEntity.ok(userService.getStatus(userId)); + } + + @PutMapping("/{user_id}/status") + public ResponseEntity updateStatus( + @PathVariable("user_id") UUID userId, + @RequestBody ChangeStatusRequest req + ) { + userService.updateStatus(userId, req.status()); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/{user_id}/role") + public ResponseEntity getRole(@PathVariable("user_id") UUID userId) { + return ResponseEntity.ok(userService.getRole(userId)); + } + + @PutMapping("/{user_id}/role") + public ResponseEntity updateRole( + @PathVariable("user_id") UUID userid, + @RequestBody ChangeRoleRequest req + ) { + userService.updateRole(userid, req.role()); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/{user_id}/exists") + public ResponseEntity exists(@PathVariable("user_id") UUID userId) { + boolean exists = userService.exists(userId); + return ResponseEntity.ok(new UserExistsResponse(exists)); + } + + @GetMapping("/me") + public ResponseEntity getMe(@RequestHeader("Authorization") String authHeader) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + String token = authHeader.substring(7); + + UUID userId; + try { + userId = jwtService.getUserId(token); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + User user = userService.getById(userId); + return ResponseEntity.ok(UserMapper.toDto(user)); + } + + @DeleteMapping("/{user_id}") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity delete(@PathVariable("user_id") UUID userId) { + userService.delete(userId); + return ResponseEntity.ok().build(); + } } diff --git a/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java b/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java deleted file mode 100644 index bb7b436..0000000 --- a/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.miarma.backend.core.dto; - -import jakarta.validation.constraints.NotBlank; - -public class LoginRequest { - @NotBlank - private String username; - - @NotBlank - private String password; - - private short serviceId; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public short getServiceId() { - return serviceId; - } - - public void setServiceId(short serviceId) { - this.serviceId = serviceId; - } -} diff --git a/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java b/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java deleted file mode 100644 index 194732a..0000000 --- a/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.miarma.backend.core.dto; - -public class LoginResponse { - private String token; - private short serviceId; - private UserDto user; - - public LoginResponse(String token, short serviceId, UserDto user) { - this.token = token; - this.serviceId = serviceId; - this.user = user; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public short getServiceId() { - return serviceId; - } - - public void setServiceId(short serviceId) { - this.serviceId = serviceId; - } - - public UserDto getUser() { - return user; - } - - public void setUser(UserDto user) { - this.user = user; - } -} \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/dto/UpdateCredentialDto.java b/core/src/main/java/net/miarma/backend/core/dto/UpdateCredentialDto.java new file mode 100644 index 0000000..be9f33e --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/dto/UpdateCredentialDto.java @@ -0,0 +1,41 @@ +package net.miarma.backend.core.dto; + +import java.util.UUID; + +public class UpdateCredentialDto { + private String email; + private String username; + private Byte status; + + public UpdateCredentialDto() { } + + public UpdateCredentialDto(String email, String username, Byte status) { + this.email = email; + this.username = username; + this.status = status; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Byte getStatus() { + return status; + } + + public void setStatus(Byte status) { + this.status = status; + } +} diff --git a/core/src/main/java/net/miarma/backend/core/dto/UserDto.java b/core/src/main/java/net/miarma/backend/core/dto/UserDto.java deleted file mode 100644 index 58dd2a2..0000000 --- a/core/src/main/java/net/miarma/backend/core/dto/UserDto.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.miarma.backend.core.dto; - -import java.time.Instant; -import java.util.UUID; - -public class UserDto { - private UUID userId; - private String displayName; - private String avatar; - private Byte globalStatus; - private Byte globalRole; - private Instant createdAt; - private Instant updatedAt; - - public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole, - Instant createdAt, Instant updatedAt) { - this.userId = userId; - this.displayName = displayName; - this.avatar = avatar; - this.globalStatus = globalStatus; - this.globalRole = globalRole; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public UUID getUserId() { - return userId; - } - - public void setUserId(UUID userId) { - this.userId = userId; - } - - 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 Byte getGlobalStatus() { - return globalStatus; - } - - public void setGlobalStatus(Byte globalStatus) { - this.globalStatus = globalStatus; - } - - public Byte getGlobalRole() { - return globalRole; - } - - public void setGlobalRole(Byte globalRole) { - this.globalRole = globalRole; - } - - public Instant getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - - public Instant getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } -} diff --git a/core/src/main/java/net/miarma/backend/core/mapper/CredentialMapper.java b/core/src/main/java/net/miarma/backend/core/mapper/CredentialMapper.java new file mode 100644 index 0000000..3b6477f --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/mapper/CredentialMapper.java @@ -0,0 +1,81 @@ +package net.miarma.backend.core.mapper; + +import net.miarma.backend.core.dto.UpdateCredentialDto; +import net.miarma.backend.core.model.Credential; +import net.miarma.backlib.dto.CreateCredentialDto; +import net.miarma.backlib.dto.CredentialDto; +import org.hibernate.sql.Update; + +import java.util.UUID; + +public class CredentialMapper { + + public static CredentialDto toDto(Credential c) { + if (c == null) return null; + + return new CredentialDto( + c.getCredentialId(), + c.getUserId(), + c.getServiceId(), + c.getUsername(), + c.getEmail(), + c.getStatus(), + c.getCreatedAt(), + c.getUpdatedAt() + ); + } + + public static CreateCredentialDto toCreateDto(Credential c) { + if (c == null) return null; + + return new CreateCredentialDto( + c.getUserId(), + c.getServiceId(), + c.getUsername(), + c.getEmail(), + c.getPassword(), + c.getStatus() + ); + } + + public static Credential toEntity(CredentialDto dto) { + if (dto == null) return null; + + Credential c = new Credential(); + c.setCredentialId(dto.getCredentialId()); + c.setUserId(dto.getUserId()); + c.setServiceId(dto.getServiceId()); + c.setUsername(dto.getUsername()); + c.setEmail(dto.getEmail()); + c.setStatus(dto.getStatus()); + c.setCreatedAt(dto.getCreatedAt()); + c.setUpdatedAt(dto.getUpdatedAt()); + return c; + } + + public static Credential toEntity(CreateCredentialDto dto) { + if (dto == null) return null; + + Credential c = new Credential(); + c.setUserId(dto.getUserId()); + c.setServiceId(dto.getServiceId()); + c.setUsername(dto.getUsername()); + c.setEmail(dto.getEmail()); + c.setPassword(dto.getPassword()); + c.setStatus(dto.getStatus()); + + return c; + } + + public static Credential toEntity(UpdateCredentialDto dto) { + if (dto == null) return null; + + Credential c = new Credential(); + c.setEmail(dto.getEmail()); + c.setUsername(dto.getUsername()); + c.setStatus(dto.getStatus()); + + return c; + } + +} diff --git a/core/src/main/java/net/miarma/backend/core/mapper/FileMapper.java b/core/src/main/java/net/miarma/backend/core/mapper/FileMapper.java new file mode 100644 index 0000000..a81868f --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/mapper/FileMapper.java @@ -0,0 +1,38 @@ +package net.miarma.backend.core.mapper; + +import net.miarma.backend.core.model.File; +import net.miarma.backlib.dto.FileDto; + +import java.time.Instant; +import java.util.UUID; + +public class FileMapper { + + private FileMapper() {} + + public static FileDto.Response toResponse(File file) { + FileDto.Response res = new FileDto.Response(); + + res.setFileId(file.getFileId()); + res.setFileName(file.getFileName()); + res.setFilePath(file.getFilePath()); + res.setMimeType(file.getMimeType()); + res.setUploadedBy(file.getUploadedBy()); + res.setUploadedAt(file.getUploadedAt()); + res.setContext(file.getContext()); + + return res; + } + + public static File toEntity(FileDto.Request req) { + File file = new File(); + + file.setFileId(UUID.randomUUID()); + file.setFileName(req.getFileName()); + file.setFilePath(req.getFilePath()); + file.setMimeType(req.getMimeType()); + file.setUploadedBy(req.getUploadedBy()); + file.setContext(req.getContext()); + return file; + } +} diff --git a/core/src/main/java/net/miarma/backend/core/mapper/UserMapper.java b/core/src/main/java/net/miarma/backend/core/mapper/UserMapper.java new file mode 100644 index 0000000..62155e1 --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/mapper/UserMapper.java @@ -0,0 +1,52 @@ +package net.miarma.backend.core.mapper; + +import net.miarma.backend.core.model.User; +import net.miarma.backlib.dto.CreateUserDto; +import net.miarma.backlib.dto.CredentialDto; +import net.miarma.backlib.dto.UserDto; +import net.miarma.backlib.dto.UserWithCredentialDto; + +import java.util.UUID; + +public class UserMapper { + public static User fromDto(UserDto dto) { + if (dto == null) return null; + + User user = new User(); + user.setDisplayName(dto.getDisplayName()); + user.setAvatar(dto.getAvatar()); + user.setGlobalRole(dto.getGlobalRole()); + user.setGlobalStatus(dto.getGlobalStatus()); + return user; + } + + public static User fromCreateDto(CreateUserDto dto) { + User user = new User(); + user.setUserId(UUID.randomUUID()); + user.setDisplayName(dto.displayName()); + user.setAvatar(dto.avatar() != null ? dto.avatar() : null); + user.setGlobalRole((byte)0); + user.setGlobalStatus((byte)1); + return user; + } + + public static UserDto toDto(User u) { + if (u == null) return null; + + return new UserDto( + u.getUserId(), + u.getDisplayName(), + u.getAvatar(), + u.getGlobalStatus(), + u.getGlobalRole(), + u.getCreatedAt(), + u.getUpdatedAt() + ); + } + + public static UserWithCredentialDto toDtoWithCredentials(UserDto user, CredentialDto account){ + if (user == null || account == null) return null; + + return new UserWithCredentialDto(user, account); + } +} diff --git a/core/src/main/java/net/miarma/backend/core/model/Credential.java b/core/src/main/java/net/miarma/backend/core/model/Credential.java index 790b109..3cf9f9e 100644 --- a/core/src/main/java/net/miarma/backend/core/model/Credential.java +++ b/core/src/main/java/net/miarma/backend/core/model/Credential.java @@ -19,79 +19,66 @@ import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.persistence.UniqueConstraint; +import net.miarma.backlib.util.UuidUtil; @Entity -@Table( - name = "credentials", - uniqueConstraints = { - @UniqueConstraint(columnNames = {"service_id", "username"}), - @UniqueConstraint(columnNames = {"service_id", "email"}) - } -) +@Table(name = "credentials", + uniqueConstraints = { + @UniqueConstraint(columnNames = { "service_id", "username" }), + @UniqueConstraint(columnNames = { "service_id", "email" }) + }) + public class Credential { - + @Id - @Column(columnDefinition = "BINARY(16)") - private byte[] credentialIdBin; + @Column(name = "credential_id", columnDefinition = "BINARY(16)") + private byte[] credentialIdBin; + + @Column(name = "user_id", columnDefinition = "BINARY(16)") + private byte[] userIdBin; + + @Transient + private UUID credentialId; + + @Transient + private UUID userId; + + @Column(name = "service_id") + private Byte serviceId; + + private String username; + private String email; + private String password; + private Byte status; - @Column(name = "user_id", columnDefinition = "BINARY(16)") - private byte[] userIdBin; - - @Transient - private UUID credentialId; + @CreationTimestamp + private Instant createdAt; + + @UpdateTimestamp + private Instant updatedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", insertable = false, updatable = false) + private User user; - @Transient - private UUID userId; - - @Column(name = "service_id") - private Byte serviceId; - - private String username; - private String email; - private String password; - - private Byte status; - - @CreationTimestamp - private Instant createdAt; - - @UpdateTimestamp - private Instant updatedAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", insertable = false, updatable = false) - private User user; - - @PrePersist + @PrePersist @PreUpdate private void prePersist() { if (credentialId != null) { - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb.putLong(credentialId.getMostSignificantBits()); - bb.putLong(credentialId.getLeastSignificantBits()); - credentialIdBin = bb.array(); + credentialIdBin = UuidUtil.uuidToBin(credentialId); } if (userId != null) { - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb.putLong(userId.getMostSignificantBits()); - bb.putLong(userId.getLeastSignificantBits()); - userIdBin = bb.array(); + userIdBin = UuidUtil.uuidToBin(userId); } } @PostLoad private void postLoad() { if (credentialIdBin != null) { - ByteBuffer bb = ByteBuffer.wrap(credentialIdBin); - long high = bb.getLong(); - long low = bb.getLong(); - credentialId = new UUID(high, low); + credentialId = UuidUtil.binToUUID(credentialIdBin); } if (userIdBin != null) { - ByteBuffer bb = ByteBuffer.wrap(userIdBin); - long high = bb.getLong(); - long low = bb.getLong(); - userId = new UUID(high, low); + userId = UuidUtil.binToUUID(userIdBin); } } @@ -174,4 +161,4 @@ public class Credential { public void setUser(User user) { this.user = user; } -} +} \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/model/File.java b/core/src/main/java/net/miarma/backend/core/model/File.java index 8d36a25..27ea0f4 100644 --- a/core/src/main/java/net/miarma/backend/core/model/File.java +++ b/core/src/main/java/net/miarma/backend/core/model/File.java @@ -6,9 +6,12 @@ import java.util.UUID; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.PostLoad; import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; import jakarta.persistence.Transient; +import net.miarma.backlib.util.UuidUtil; @Entity @Table(name = "files") @@ -40,15 +43,28 @@ public class File { private Instant uploadedAt; @Column(name = "context", nullable = false) - private Short context; + private Byte context; @PrePersist - public void prePersist() { - if (fileId == null) { - fileId = UUID.randomUUID(); + @PreUpdate + private void prePersist() { + if (fileId != null) { + fileIdBin = UuidUtil.uuidToBin(fileId); } - if (uploadedAt == null) { - uploadedAt = Instant.now(); + + if (uploadedBy != null) { + uploadedByBin = UuidUtil.uuidToBin(uploadedBy); + } + } + + @PostLoad + private void postLoad() { + if (fileIdBin != null) { + fileId = UuidUtil.binToUUID(fileIdBin); + } + + if (uploadedByBin != null) { + uploadedBy = UuidUtil.binToUUID(uploadedByBin); } } @@ -96,11 +112,11 @@ public class File { return uploadedAt; } - public Short getContext() { + public Byte getContext() { return context; } - public void setContext(Short context) { + public void setContext(Byte context) { this.context = context; } } diff --git a/core/src/main/java/net/miarma/backend/core/model/User.java b/core/src/main/java/net/miarma/backend/core/model/User.java index 5022bc3..d5edc3b 100644 --- a/core/src/main/java/net/miarma/backend/core/model/User.java +++ b/core/src/main/java/net/miarma/backend/core/model/User.java @@ -1,6 +1,5 @@ package net.miarma.backend.core.model; -import java.nio.ByteBuffer; import java.time.Instant; import java.util.UUID; @@ -15,6 +14,7 @@ import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; import jakarta.persistence.Transient; +import net.miarma.backlib.util.UuidUtil; @Entity @Table(name = "users") @@ -48,20 +48,14 @@ public class User { @PreUpdate private void prePersist() { if (userId != null) { - ByteBuffer bb = ByteBuffer.wrap(new byte[16]); - bb.putLong(userId.getMostSignificantBits()); - bb.putLong(userId.getLeastSignificantBits()); - userIdBin = bb.array(); + userIdBin = UuidUtil.uuidToBin(userId); } } @PostLoad private void postLoad() { if (userIdBin != null) { - ByteBuffer bb = ByteBuffer.wrap(userIdBin); - long high = bb.getLong(); - long low = bb.getLong(); - userId = new UUID(high, low); + userId = UuidUtil.binToUUID(userIdBin); } } diff --git a/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java b/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java index a082a50..1858076 100644 --- a/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java +++ b/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java @@ -4,19 +4,59 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import jakarta.validation.constraints.NotBlank; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import net.miarma.backend.core.model.Credential; -public interface CredentialRepository extends JpaRepository { +public interface CredentialRepository extends JpaRepository { - Optional findByServiceIdAndUsername(short serviceId, String username); + @Query(""" + SELECT c FROM Credential c + JOIN FETCH c.user + WHERE c.serviceId = :serviceId + AND c.username = :username + """) + Optional findByServiceIdAndUsername(@Param("serviceId") Byte serviceId, + @Param("username") String username); - Optional findByServiceIdAndEmail(short serviceId, String email); + List findAllByServiceId(Byte serviceId); - Optional findByUserIdAndServiceId(UUID userId, short serviceId); + @Query("SELECT c FROM Credential c JOIN FETCH c.user WHERE c.serviceId = :serviceId") + List getByServiceIdFetchUser(@Param("serviceId") Byte serviceId); + + Optional findByServiceIdAndEmail(Byte serviceId, String email); + + @Query("SELECT c FROM Credential c WHERE c.userIdBin = :userIdBin AND c.serviceId = :serviceId") + Optional findByUserIdAndServiceId(@Param("userIdBin") byte[] userIdBin, @Param("serviceId") Byte serviceId); - List findByUserId(UUID userId); + Optional findByUsernameAndServiceId(String username, Byte serviceId); + @Query("SELECT c FROM Credential c WHERE c.userIdBin = :userIdBin") + List findByUserId(@Param("userIdBin") byte[] userIdBin); + + List findByEmail(String email); + + boolean existsByUsernameAndServiceId(String username, Byte serviceId); + + boolean existsByEmailAndServiceId(String email, Byte serviceId); + + @Modifying + @Query(""" + UPDATE Credential c + SET c.username = :username, + c.email = :email, + c.status = :status + WHERE c.credentialIdBin = :credentialIdBin + """) + int update(@Param("credentialIdBin") byte[] credentialIdBin, + @Param("username") String username, + @Param("email") String email, + @Param("status") Byte status); + + List findByUsername(@NotBlank String username); } diff --git a/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java b/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java index 95919ba..c984a31 100644 --- a/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java +++ b/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java @@ -1,20 +1,16 @@ package net.miarma.backend.core.repository; import java.util.List; -import java.util.Optional; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import net.miarma.backend.core.model.File; -public interface FileRepository extends JpaRepository { - - Optional findById(UUID fileId); - +public interface FileRepository extends JpaRepository { List findByUploadedBy(UUID uploadedBy); - List findByContext(short context); + List findByContext(Byte context); - List findByUploadedByAndContext(UUID uploadedBy, short context); + List findByUploadedByAndContext(UUID uploadedBy, Byte context); } \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java b/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java index 3973cd0..da95016 100644 --- a/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java +++ b/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java @@ -1,11 +1,9 @@ package net.miarma.backend.core.repository; -import java.util.UUID; - import org.springframework.data.jpa.repository.JpaRepository; import net.miarma.backend.core.model.User; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { } diff --git a/core/src/main/java/net/miarma/backend/core/security/CorePrincipal.java b/core/src/main/java/net/miarma/backend/core/security/CorePrincipal.java new file mode 100644 index 0000000..50d694d --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/security/CorePrincipal.java @@ -0,0 +1,46 @@ +package net.miarma.backend.core.security; + +import net.miarma.backlib.security.CoreAuthTokenHolder; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +public class CorePrincipal implements UserDetails { + private final UUID userId; + private final Byte role; + + public CorePrincipal(UUID userId, Byte role) { + this.userId = userId; + this.role = role; + } + + public UUID getUserId() { return userId; } + public Byte getRole() { return role; } + + @Override + public Collection getAuthorities() { + List auth = new ArrayList<>(); + + String roleName = switch(role) { + case 0 -> "USER"; + case 1 -> "ADMIN"; + default -> "GUEST"; + }; + + auth.add(new SimpleGrantedAuthority("ROLE_" + roleName)); + + return auth; + } + + @Override public String getPassword() { return ""; } + @Override public String getUsername() { return userId.toString(); } + @Override public boolean isAccountNonExpired() { return true; } + @Override public boolean isAccountNonLocked() { return true; } + @Override public boolean isCredentialsNonExpired() { return true; } + @Override public boolean isEnabled() { return true; } +} diff --git a/core/src/main/java/net/miarma/backend/core/config/JwtFilter.java b/core/src/main/java/net/miarma/backend/core/security/JwtFilter.java similarity index 78% rename from core/src/main/java/net/miarma/backend/core/config/JwtFilter.java rename to core/src/main/java/net/miarma/backend/core/security/JwtFilter.java index 3bd250d..2e4959b 100644 --- a/core/src/main/java/net/miarma/backend/core/config/JwtFilter.java +++ b/core/src/main/java/net/miarma/backend/core/security/JwtFilter.java @@ -1,4 +1,4 @@ -package net.miarma.backend.core.config; +package net.miarma.backend.core.security; import java.io.IOException; import java.util.List; @@ -8,7 +8,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -17,7 +16,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.miarma.backend.core.model.User; -import net.miarma.backend.core.service.JwtService; +import net.miarma.backlib.security.JwtService; import net.miarma.backend.core.service.UserService; @Component @@ -36,7 +35,6 @@ public class JwtFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); @@ -44,17 +42,11 @@ public class JwtFilter extends OncePerRequestFilter { try { if (jwtService.validateToken(token)) { UUID userId = jwtService.getUserId(token); - short serviceId = jwtService.getServiceId(token); + Byte serviceId = jwtService.getServiceId(token); User user = userService.getById(userId); - - List authorities = List.of( - new SimpleGrantedAuthority("ROLE_" + user.getGlobalRole()) - ); - - UsernamePasswordAuthenticationToken auth = - new UsernamePasswordAuthenticationToken(user, null, authorities); - auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + CorePrincipal principal = new CorePrincipal(userId, user.getGlobalRole()); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis(); @@ -68,7 +60,7 @@ public class JwtFilter extends OncePerRequestFilter { return; } } - + filterChain.doFilter(request, response); } } \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/service/AuthService.java b/core/src/main/java/net/miarma/backend/core/service/AuthService.java index 754aa82..4721330 100644 --- a/core/src/main/java/net/miarma/backend/core/service/AuthService.java +++ b/core/src/main/java/net/miarma/backend/core/service/AuthService.java @@ -1,46 +1,112 @@ package net.miarma.backend.core.service; +import java.util.Optional; +import java.util.UUID; + +import net.miarma.backlib.dto.*; +import net.miarma.backlib.exception.*; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import net.miarma.backend.core.dto.LoginRequest; -import net.miarma.backend.core.dto.LoginResponse; -import net.miarma.backend.core.dto.UserDto; +import net.miarma.backend.core.mapper.CredentialMapper; +import net.miarma.backend.core.mapper.UserMapper; import net.miarma.backend.core.model.Credential; +import net.miarma.backend.core.model.User; +import tools.jackson.databind.JsonNode; @Service public class AuthService { private final CredentialService credentialService; - private final JwtService jwtService; + private final UserService userService; + private final net.miarma.backlib.security.JwtService jwtService; private final PasswordEncoder passwordEncoder; - public AuthService(CredentialService credentialService, JwtService jwtService, - PasswordEncoder passwordEncoder) { + public AuthService(CredentialService credentialService, UserService userService, + net.miarma.backlib.security.JwtService jwtService, PasswordEncoder passwordEncoder) { this.credentialService = credentialService; + this.userService = userService; this.jwtService = jwtService; this.passwordEncoder = passwordEncoder; } public LoginResponse login(LoginRequest request) { - Credential cred = credentialService.getByUserIdAndService(request.getServiceId(), request.getUsername()); + Credential cred = credentialService.getForLogin(request.serviceId(), request.username()); - if (!passwordEncoder.matches(request.getPassword(), cred.getPassword())) { - throw new RuntimeException("Invalid credentials"); + if (!passwordEncoder.matches(request.password(), cred.getPassword())) { + throw new UnauthorizedException("Credenciales no válidas"); } - String token = jwtService.generateToken(cred.getUserId(), request.getServiceId()); + if (cred.getStatus() == 0) { + throw new ForbiddenException("Esa cuenta está desactivada"); + } - UserDto userDto = new UserDto( - cred.getUser().getUserId(), - cred.getUser().getDisplayName(), - cred.getUser().getAvatar(), - cred.getUser().getGlobalStatus(), - cred.getUser().getGlobalRole(), - cred.getUser().getCreatedAt(), - cred.getUser().getUpdatedAt() - ); + String token = jwtService.generateToken(cred.getUserId(), request.serviceId()); + UserDto userDto = UserMapper.toDto(cred.getUser()); + CredentialDto credentialDto = CredentialMapper.toDto(cred); - return new LoginResponse(token, request.getServiceId(), userDto); + return new LoginResponse(token, userDto, credentialDto); + } + + public LoginResponse register(RegisterRequest request) { + UUID userIdByUsername = null; + UUID userIdByEmail = null; + + Optional credByUsername = credentialService.getByUsername(request.username()).stream().findFirst(); + if(credByUsername.isPresent()) { + userIdByUsername = credByUsername.get().getUserId(); + } + + try { + userIdByEmail = credentialService.getByEmail(request.email()).get(0).getUserId(); + } catch (NotFoundException e) { } + + User user; + if (userIdByUsername != null && userIdByEmail != null) { + if (!userIdByUsername.equals(userIdByEmail)) { + throw new ConflictException("Username y email ya existen pero pertenecen a usuarios distintos"); + } + user = userService.getById(userIdByUsername); + } else if (userIdByUsername != null) { + user = userService.getById(userIdByUsername); + } else if (userIdByEmail != null) { + user = userService.getById(userIdByEmail); + } else { + CreateUserDto dto = new CreateUserDto(request.displayName(), null); + user = userService.create(UserMapper.fromCreateDto(dto)); + } + + Credential cred = new Credential(); + cred.setCredentialId(UUID.randomUUID()); + cred.setUserId(user.getUserId()); + cred.setUser(user); + cred.setServiceId(request.serviceId()); + cred.setUsername(request.username()); + cred.setEmail(request.email()); + cred.setPassword(request.password()); + cred.setStatus((byte)1); + credentialService.create(cred); + + String token = jwtService.generateToken(user.getUserId(), request.serviceId()); + + return new LoginResponse(token, UserMapper.toDto(user), CredentialMapper.toDto(cred)); + } + + public void changePassword(UUID userId, ChangePasswordRequest request) { + Credential cred = credentialService.getByUserId(userId) + .stream() + .filter(c -> c.getServiceId().equals(request.serviceId())) + .findFirst() + .orElseThrow(() -> new NotFoundException("Cuenta no encontrada")); + + if (!passwordEncoder.matches(request.oldPassword(), cred.getPassword())) { + throw new ValidationException("oldPassword", "La contraseña actual es incorrecta"); + } + + if (request.newPassword().length() < 8) { + throw new ValidationException("newPassword", "La nueva contraseña debe tener al menos 8 caracteres"); + } + + credentialService.updatePassword(cred.getCredentialId(), request); } } diff --git a/core/src/main/java/net/miarma/backend/core/service/CredentialService.java b/core/src/main/java/net/miarma/backend/core/service/CredentialService.java index 7d16a68..43bc37b 100644 --- a/core/src/main/java/net/miarma/backend/core/service/CredentialService.java +++ b/core/src/main/java/net/miarma/backend/core/service/CredentialService.java @@ -3,47 +3,164 @@ package net.miarma.backend.core.service; import java.util.List; import java.util.UUID; +import jakarta.validation.constraints.NotBlank; +import net.miarma.backlib.exception.*; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import net.miarma.backend.core.model.Credential; +import net.miarma.backend.core.model.User; import net.miarma.backend.core.repository.CredentialRepository; +import net.miarma.backlib.dto.ChangePasswordRequest; +import net.miarma.backlib.dto.CredentialDto; +import net.miarma.backlib.util.UuidUtil; @Service @Transactional public class CredentialService { private final CredentialRepository credentialRepository; + private final UserService userService; + private final PasswordEncoder passwordEncoder; - public CredentialService(CredentialRepository credentialRepository) { + public CredentialService(CredentialRepository credentialRepository, UserService userService, PasswordEncoder passwordEncoder) { this.credentialRepository = credentialRepository; + this.userService = userService; + this.passwordEncoder = passwordEncoder; } - public Credential getById(UUID id) { - return credentialRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Credential not found")); + public Credential getById(UUID credentialId) { + byte[] idBytes = UuidUtil.uuidToBin(credentialId); + return credentialRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Cuenta no encontrada")); } public Credential create(Credential credential) { - // TODO: validate duplicates here + if (credential.getUsername() == null || credential.getUsername().isBlank()) { + throw new ValidationException("userName", "El usuario no puede estar vacío"); + } + if (credential.getEmail() == null || !credential.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) { + throw new ValidationException("email", "Formato de email no válido"); + } + if (credential.getPassword() == null || credential.getPassword().length() < 6) { + throw new ValidationException("password", "La contraseña tiene que tener al menos 6 caracteres"); + } + if (credential.getServiceId() == null || credential.getServiceId() < 0) { + throw new ValidationException("serviceId", "El identificador de servicio debe ser positivo"); + } + + boolean existsUsername = credentialRepository.existsByUsernameAndServiceId( + credential.getUsername(), credential.getServiceId()); + if (existsUsername) throw new ConflictException("El usuario ya existe para este servicio"); + + boolean existsEmail = credentialRepository.existsByEmailAndServiceId( + credential.getEmail(), credential.getServiceId()); + if (existsEmail) throw new ConflictException("El email ya existe para este servicio"); + + credential.setCredentialId(UUID.randomUUID()); + credential.setPassword(passwordEncoder.encode(credential.getPassword())); return credentialRepository.save(credential); } + public List getAll() { + return credentialRepository.findAll(); + } + + public List getByServiceId(Byte serviceId) { + return credentialRepository.findAllByServiceId(serviceId); + } + + public List getByServiceIdFetchUser(Byte serviceId) { + return credentialRepository.getByServiceIdFetchUser(serviceId); + } + public List getByUserId(UUID userId) { - List creds = credentialRepository.findByUserId(userId); + List creds = credentialRepository.findByUserId(UuidUtil.uuidToBin(userId)); if (creds.isEmpty()) { - throw new RuntimeException("User has no credentials"); + throw new NotFoundException("El usuario no tiene cuenta"); } return creds; } + + public List getByUsername(@NotBlank String username) { + return credentialRepository.findByUsername(username); + } - public Credential getByUserIdAndService(short serviceId, String username) { - return credentialRepository.findByServiceIdAndUsername(serviceId, username) - .orElseThrow(() -> new RuntimeException("Credential not found in this site")); + public List getByEmail(String email) { + return credentialRepository.findByEmail(email).stream() + .toList(); + } + + public Credential getByUserIdAndService(UUID userId, Byte serviceId) { + return credentialRepository.findByUserIdAndServiceId(UuidUtil.uuidToBin(userId), serviceId) + .orElseThrow(() -> new NotFoundException("El usuario no tiene cuenta en este sitio")); } - public Credential getForLogin(short serviceId, String username) { + public Credential getForLogin(Byte serviceId, String username) { return credentialRepository.findByServiceIdAndUsername(serviceId, username) - .orElseThrow(() -> new RuntimeException("Invalid credentials")); + .orElseThrow(() -> new BadRequestException("Credenciales no válidas")); + } + + public boolean existsByUsernameAndService(String username, Byte serviceId) { + return credentialRepository.findByUsernameAndServiceId(username, serviceId).isPresent(); + } + + public boolean isOwner(UUID credentialId, UUID userId) { + byte[] idBytes = UuidUtil.uuidToBin(credentialId); + + return credentialRepository.findById(idBytes) + .map(c -> c.getUserId().equals(userId)) + .orElse(false); + } + + public Credential update(UUID credentialId, Credential changes) { + Credential cred = getById(credentialId); + + if (cred.getStatus() == (byte)0) { + throw new ForbiddenException("La cuenta está inactiva, contacta con un administrador"); + } + + if (changes.getUsername() != null) cred.setUsername(changes.getUsername()); + if (changes.getEmail() != null) cred.setEmail(changes.getEmail()); + if (changes.getStatus() != null) cred.setStatus(changes.getStatus()); + + int updated = credentialRepository.update( + UuidUtil.uuidToBin(cred.getCredentialId()), + cred.getUsername(), + cred.getEmail(), + cred.getStatus() + ); + + if (updated == 0) + throw new RuntimeException("No se pudo actualizar la cuenta"); + + return getById(credentialId); + } + + public Credential updatePassword(UUID credentialId, ChangePasswordRequest request) { + byte[] idBytes = UuidUtil.uuidToBin(credentialId); + + Credential cred = credentialRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Cuenta no encontrada")); + + if (!passwordEncoder.matches(request.oldPassword(), cred.getPassword())) { + throw new ValidationException("oldPassword", "La contraseña actual es incorrecta"); + } + + cred.setPassword(passwordEncoder.encode(request.newPassword())); + return credentialRepository.save(cred); + } + + public void delete(UUID credentialId) { + byte[] idBytes = UuidUtil.uuidToBin(credentialId); + if(!credentialRepository.existsById(idBytes)) + throw new NotFoundException("Cuenta no encontrada"); + credentialRepository.deleteById(idBytes); + } + + public Byte getStatus(UUID userId, Byte serviceId) { + Credential credential = getByUserIdAndService(userId, serviceId); + return credential.getStatus(); } } diff --git a/core/src/main/java/net/miarma/backend/core/service/FileService.java b/core/src/main/java/net/miarma/backend/core/service/FileService.java index 91001a1..bf06d5a 100644 --- a/core/src/main/java/net/miarma/backend/core/service/FileService.java +++ b/core/src/main/java/net/miarma/backend/core/service/FileService.java @@ -1,13 +1,24 @@ package net.miarma.backend.core.service; +import java.io.FileOutputStream; +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.Map; import java.util.UUID; +import net.miarma.backend.core.mapper.FileMapper; +import net.miarma.backlib.dto.FileDto; +import net.miarma.backlib.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import net.miarma.backend.core.model.File; import net.miarma.backend.core.repository.FileRepository; +import net.miarma.backlib.util.UuidUtil; @Service @Transactional @@ -15,32 +26,65 @@ public class FileService { private final FileRepository fileRepository; + @Value("${filesDir}") + private String filesDir; + public FileService(FileRepository fileRepository) { this.fileRepository = fileRepository; } - public File get(UUID fileId) { - return fileRepository.findById(fileId) - .orElseThrow(() -> new RuntimeException("File not found")); + public File getById(UUID fileId) { + byte[] idBytes = UuidUtil.uuidToBin(fileId); + return fileRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Archivo no encontrado")); } - public List listByUser(UUID userId) { + public List getAll() { + return fileRepository.findAll(); + } + + public List getByUserId(UUID userId) { return fileRepository.findByUploadedBy(userId); } - public List listByContext(short context) { - return fileRepository.findByContext(context); - } + public File create(File file, byte[] fileBinary) throws IOException { + Path dirPath = Paths.get(filesDir, String.valueOf(file.getContext())); + if (!Files.exists(dirPath)) { + Files.createDirectories(dirPath); + } + + Path filePath = dirPath.resolve(file.getFileName()); + try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) { + fos.write(fileBinary); + } + + file.setFilePath(filePath.toString()); - public File create(File file) { return fileRepository.save(file); } - public void delete(UUID fileId) { - if (!fileRepository.existsById(fileId)) { - throw new RuntimeException("File not found"); + public File update(UUID fileId, File file) { + byte[] idBytes = UuidUtil.uuidToBin(fileId); + if (!fileRepository.existsById(idBytes)) { + throw new NotFoundException("Archivo no encontrado"); } - fileRepository.deleteById(fileId); + return fileRepository.save(file); } + + public void delete(UUID fileId) { + byte[] idBytes = UuidUtil.uuidToBin(fileId); + if (!fileRepository.existsById(idBytes)) { + throw new NotFoundException("Archivo no encontrado"); + } + fileRepository.deleteById(idBytes); + } + + public boolean isOwner(UUID fileId, UUID userId) { + byte[] fileBytes = UuidUtil.uuidToBin(fileId); + return fileRepository.findById(fileBytes) + .map(f -> f.getUploadedBy().equals(userId)) + .orElse(false); + } } + diff --git a/core/src/main/java/net/miarma/backend/core/service/JwtService.java b/core/src/main/java/net/miarma/backend/core/service/JwtService.java deleted file mode 100644 index fa87a6e..0000000 --- a/core/src/main/java/net/miarma/backend/core/service/JwtService.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.miarma.backend.core.service; - -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; -import org.springframework.stereotype.Service; - -import java.security.Key; -import java.util.Date; -import java.util.UUID; - -@Service -public class JwtService { - - private final String secret = "miarma-esto-es-un-secreto-super-largo-para-jwt-1234567890"; - private final Key key = Keys.hmacShaKeyFor(secret.getBytes()); - - private final long expiration = 3600_000; - - public String generateToken(UUID userId, short serviceId) { - Date now = new Date(); - Date exp = new Date(now.getTime() + expiration); - - return Jwts.builder() - .setSubject(userId.toString()) - .claim("service", serviceId) - .setIssuedAt(now) - .setExpiration(exp) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); - } - - public boolean validateToken(String token) { - try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); - return true; - } catch (JwtException | IllegalArgumentException e) { - return false; - } - } - - public UUID getUserId(String token) { - Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); - return UUID.fromString(claims.getSubject()); - } - - public short getServiceId(String token) { - Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); - return ((Number) claims.get("service")).shortValue(); - } - - public Date getExpiration(String token) { - Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); - return claims.getExpiration(); - } -} - diff --git a/core/src/main/java/net/miarma/backend/core/service/UserService.java b/core/src/main/java/net/miarma/backend/core/service/UserService.java index a9b9626..3020eba 100644 --- a/core/src/main/java/net/miarma/backend/core/service/UserService.java +++ b/core/src/main/java/net/miarma/backend/core/service/UserService.java @@ -1,12 +1,21 @@ package net.miarma.backend.core.service; +import java.util.List; import java.util.UUID; +import net.miarma.backend.core.mapper.UserMapper; +import net.miarma.backend.core.security.CorePrincipal; +import net.miarma.backlib.dto.ChangeAvatarRequest; +import net.miarma.backlib.exception.NotFoundException; +import net.miarma.backlib.exception.ValidationException; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import jakarta.transaction.Transactional; import net.miarma.backend.core.model.User; import net.miarma.backend.core.repository.UserRepository; +import net.miarma.backlib.dto.UserDto; +import net.miarma.backlib.util.UuidUtil; @Service @Transactional @@ -17,25 +26,88 @@ public class UserService { this.userRepository = userRepository; } + public List getAll() { + return userRepository.findAll(); + } + public User getById(UUID userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new RuntimeException("User not found")); + byte[] idBytes = UuidUtil.uuidToBin(userId); + return userRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado")); } - - public User create(User user) { - // TODO: basic validation - return userRepository.save(user); - } - - public User update(User user) { - if(!userRepository.existsById(user.getUserId())) - throw new RuntimeException("User not found"); - return userRepository.save(user); - } - - public void delete(UUID userId) { - if(!userRepository.existsById(userId)) - throw new RuntimeException("User not found"); - userRepository.deleteById(userId); + + public User create(User user) { + if(user.getDisplayName() == null || user.getDisplayName().isBlank()) { + throw new ValidationException("displayName", "El nombre a mostrar es necesario"); + } + return userRepository.save(user); + } + + public User update(UUID userId, User changes) { + User user = userRepository.findById(UuidUtil.uuidToBin(userId)) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado")); + + if (changes.getDisplayName() != null) { + String dn = changes.getDisplayName().trim(); + if (dn.isEmpty()) throw new ValidationException("displayName", "No puede estar vacío"); + if (dn.length() > 50) throw new ValidationException("displayName", "Máx 50 caracteres"); + user.setDisplayName(dn); + } + + if (changes.getAvatar() != null) + user.setAvatar(changes.getAvatar()); + + if (changes.getGlobalRole() != null) + user.setGlobalRole(changes.getGlobalRole()); + + if (changes.getGlobalStatus() != null) + user.setGlobalStatus(changes.getGlobalStatus()); + + return userRepository.save(user); + } + + public void delete(UUID userId) { + byte[] idBytes = UuidUtil.uuidToBin(userId); + if(!userRepository.existsById(idBytes)) + throw new NotFoundException("Usuario no encontrado"); + userRepository.deleteById(idBytes); } + + public UserDto updateAvatar(UUID userId, ChangeAvatarRequest req) { + User user = userRepository.findById(UuidUtil.uuidToBin(userId)) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado")); + user.setAvatar(req.avatar()); + userRepository.save(user); + return UserMapper.toDto(user); + } + + public Byte getStatus(UUID userId) { + User user = userRepository.findById(UuidUtil.uuidToBin(userId)) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado"));; + return user.getGlobalStatus(); + } + + public void updateStatus(UUID userId, Byte status) { + User user = userRepository.findById(UuidUtil.uuidToBin(userId)) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado"));; + user.setGlobalStatus(status); + userRepository.save(user); + } + + public Byte getRole(UUID userId) { + User user = userRepository.findById(UuidUtil.uuidToBin(userId)) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado"));; + return user.getGlobalRole(); + } + + public void updateRole(UUID userId, Byte role) { + User user = userRepository.findById(UuidUtil.uuidToBin(userId)) + .orElseThrow(() -> new NotFoundException("Usuario no encontrado"));; + user.setGlobalRole(role); + userRepository.save(user); + } + + public boolean exists(UUID userId) { + return userRepository.existsById(UuidUtil.uuidToBin(userId)); + } } diff --git a/core/src/main/resources/application-dev.yml b/core/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e59dee3 --- /dev/null +++ b/core/src/main/resources/application-dev.yml @@ -0,0 +1,23 @@ +server: + port: 8080 + servlet: + context-path: /v2/core + +spring: + datasource: + url: jdbc:mariadb://localhost:3306/miarma_v2 + username: admin + password: ${DB_PASS} + driver-class-name: org.mariadb.jdbc.Driver + +logging: + level: + org.hibernate.SQL: DEBUG + org.hibernate.orm.jdbc.bind: TRACE + org.springframework.security: DEBUG + +filesDir: "/home/jomaa/.config/miarma-backend/files" + +jwt: + private-key-path: /home/jomaa/.config/miarma-backend/private.pem + public-key-path: /home/jomaa/.config/miarma-backend/public.pem diff --git a/core/src/main/resources/application-prod.yml b/core/src/main/resources/application-prod.yml new file mode 100644 index 0000000..0dc5436 --- /dev/null +++ b/core/src/main/resources/application-prod.yml @@ -0,0 +1,22 @@ +server: + port: 8080 + servlet: + context-path: /v2/core + +spring: + datasource: + url: jdbc:mariadb://mariadb:3306/miarma_v2 + username: ${DB_USER} + password: ${DB_PASS} + driver-class-name: org.mariadb.jdbc.Driver + +logging: + level: + org.springframework.security: INFO + org.hibernate.SQL: WARN + +filesDir: "/files" + +jwt: + private-key-path: ${JWT_PRIVATE_KEY} + public-key-path: ${JWT_PUBLIC_KEY} diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index 99c98a4..520b00a 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -1,12 +1,25 @@ spring: - datasource: - url: jdbc:mariadb://localhost:3306/miarma_v2 - username: USER - password: PASS + application: + name: core-service + jpa: + open-in-view: false hibernate: - ddl-auto: update - show-sql: true + ddl-auto: validate properties: hibernate: - format_sql: true + jdbc: + time_zone: UTC + + jackson: + default-property-inclusion: non_null + time-zone: Europe/Madrid + +jwt: + expiration-ms: 3600000 + +management: + endpoints: + web: + exposure: + include: health,info diff --git a/huertos/.classpath b/huertos/.classpath deleted file mode 100644 index f7e4a1d..0000000 --- a/huertos/.classpath +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/huertos/.project b/huertos/.project deleted file mode 100644 index 85aa542..0000000 --- a/huertos/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - huertos - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/huertos/.settings/org.eclipse.jdt.core.prefs b/huertos/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 2f5cc74..0000000 --- a/huertos/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/huertos/.settings/org.eclipse.m2e.core.prefs b/huertos/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/huertos/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/huertos/huertos.iml b/huertos/huertos.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/huertos/huertos.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/huertos/pom.xml b/huertos/pom.xml index 54dd97b..4f9636b 100644 --- a/huertos/pom.xml +++ b/huertos/pom.xml @@ -9,6 +9,18 @@ huertos + + 25 + 25 + + + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + @@ -28,6 +40,21 @@ mariadb-java-client runtime + + org.springframework.boot + spring-boot-http-client + 4.0.1 + compile + + + + org.springframework.boot + spring-boot-starter-cache + + + com.github.ben-manes.caffeine + caffeine + io.jsonwebtoken @@ -46,6 +73,30 @@ 0.11.5 runtime - + + net.miarma + backlib + 1.1.0 + compile + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + \ No newline at end of file diff --git a/huertos/src/main/java/net/miarma/backend/huertos/HuertosApplication.java b/huertos/src/main/java/net/miarma/backend/huertos/HuertosApplication.java new file mode 100644 index 0000000..435b0d2 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/HuertosApplication.java @@ -0,0 +1,16 @@ +package net.miarma.backend.huertos; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +@EnableCaching +@SpringBootApplication(scanBasePackages = { + "net.miarma.backend.huertos", + "net.miarma.backlib" +}) +public class HuertosApplication { + public static void main(String[] args) { + SpringApplication.run(HuertosApplication.class, args); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/client/CoreAuthClient.java b/huertos/src/main/java/net/miarma/backend/huertos/client/CoreAuthClient.java new file mode 100644 index 0000000..1808fbc --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/client/CoreAuthClient.java @@ -0,0 +1,73 @@ +package net.miarma.backend.huertos.client; + +import net.miarma.backlib.dto.ApiErrorDto; +import net.miarma.backlib.dto.LoginRequest; +import net.miarma.backlib.dto.LoginResponse; +import net.miarma.backlib.exception.*; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +@Component +public class CoreAuthClient { + + private final RestTemplate restTemplate; + private final String coreUrl; + + public CoreAuthClient( + @Qualifier("authRestTemplate") RestTemplate restTemplate, + @Value("${core.url}") String coreUrl + ) { + this.restTemplate = restTemplate; + this.coreUrl = coreUrl; + } + + + public LoginResponse login(LoginRequest req) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(req, headers); + + ResponseEntity response = restTemplate.exchange( + coreUrl + "/auth/login", + HttpMethod.POST, + requestEntity, + LoginResponse.class + ); + + if (!response.getStatusCode().is2xxSuccessful()) { + handleError(response); + } + + return response.getBody(); + } + + private void handleError(ResponseEntity response) { + HttpStatusCode statusCode = response.getStatusCode(); + + if (statusCode.equals(HttpStatus.UNAUTHORIZED)) { + throw new UnauthorizedException("Credenciales no válidas"); + } else if (statusCode.equals(HttpStatus.FORBIDDEN)) { + throw new ForbiddenException("Esa cuenta está desactivada"); + } else if (statusCode.equals(HttpStatus.NOT_FOUND)) { + throw new NotFoundException("No encontrado"); + } else if (statusCode.equals(HttpStatus.BAD_REQUEST)) { + throw new BadRequestException("Datos de solicitud faltantes"); + } else if (statusCode.equals(HttpStatus.CONFLICT)) { + throw new ConflictException("Ya existe"); + } else if (statusCode.equals(HttpStatus.UNPROCESSABLE_CONTENT)) { + throw new ValidationException("general", "Los datos no tienen formato válido"); + } else { + if (statusCode.is4xxClientError()) { + throw new BadRequestException(response.getBody().toString()); + } else { + throw new RuntimeException("Error desconocido"); + } + } + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/client/HuertosWebClient.java b/huertos/src/main/java/net/miarma/backend/huertos/client/HuertosWebClient.java new file mode 100644 index 0000000..b7650cf --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/client/HuertosWebClient.java @@ -0,0 +1,191 @@ +package net.miarma.backend.huertos.client; + +import net.miarma.backend.huertos.dto.RequestMetadataDto; +import net.miarma.backend.huertos.model.RequestMetadata; +import net.miarma.backend.huertos.util.UsernameGenerator; +import net.miarma.backlib.dto.*; +import net.miarma.backlib.exception.*; +import net.miarma.backlib.security.PasswordGenerator; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import tools.jackson.databind.ObjectMapper; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@Component +public class HuertosWebClient { + + private final RestTemplate restTemplate; + private final String coreUrl; + private final ObjectMapper objectMapper; + + public HuertosWebClient(@Qualifier("secureRestTemplate") RestTemplate restTemplate, + @Value("${core.url}") String coreUrl, + ObjectMapper objectMapper) { + this.restTemplate = restTemplate; + this.coreUrl = coreUrl; + this.objectMapper = objectMapper; + } + + public UserWithCredentialDto getUserWithCredential(UUID userId, Byte serviceId) { + ResponseEntity response = restTemplate.exchange( + coreUrl + "/users/{user_id}/service/{service_id}", + HttpMethod.GET, + null, + UserWithCredentialDto.class, + userId, serviceId + ); + + if (!response.getStatusCode().is2xxSuccessful()) { + handleError(response); + } + + return response.getBody(); + } + + public List getAllUsersWithCredentials(Byte serviceId) { + ResponseEntity response = restTemplate.exchange( + coreUrl + "/users/service/{service_id}", + HttpMethod.GET, + null, + UserWithCredentialDto[].class, + serviceId + ); + + if (!response.getStatusCode().is2xxSuccessful()) { + handleError(response); + } + + UserWithCredentialDto[] arr = response.getBody(); + return arr == null ? List.of() : Arrays.asList(arr); + } + + public UserWithCredentialDto createUser(RequestMetadataDto metadataDto) { + // 1. Crear el usuario + CreateUserDto userDto = new CreateUserDto(metadataDto.displayName(), null); + HttpEntity userRequestEntity = new HttpEntity<>(userDto); + + ResponseEntity userResponse = restTemplate.exchange( + coreUrl + "/users", + HttpMethod.POST, + userRequestEntity, + UserDto.class + ); + + if (!userResponse.getStatusCode().is2xxSuccessful()) { + handleError(userResponse); + } + + UserDto createdUser = userResponse.getBody(); + if (createdUser == null) { + throw new RuntimeException("No se pudo crear al usuario"); + } + + CreateCredentialDto credDto = new CreateCredentialDto( + createdUser.getUserId(), + (byte) 1, + UsernameGenerator.generate(metadataDto.displayName(), metadataDto.memberNumber()), + metadataDto.email(), + PasswordGenerator.generate(8), + (byte) 1 + ); + + HttpEntity credRequestEntity = new HttpEntity<>(credDto); + + ResponseEntity credResponse = restTemplate.exchange( + coreUrl + "/credentials", + HttpMethod.POST, + credRequestEntity, + CredentialDto.class + ); + + if (!credResponse.getStatusCode().is2xxSuccessful()) { + handleError(credResponse); + } + + CredentialDto createdCred = credResponse.getBody(); + if (createdCred == null) { + throw new RuntimeException("No se pudo crear la cuenta del usuario"); + } + + return new UserWithCredentialDto(createdUser, createdCred); + } + + public void deleteUser(UUID userId) { + ResponseEntity response = restTemplate.exchange( + coreUrl + "/users/{user_id}", + HttpMethod.DELETE, + null, + Void.class, + userId + ); + + if (!response.getStatusCode().is2xxSuccessful()) { + if (response.getStatusCode() != HttpStatus.NOT_FOUND) { + handleError(response); + } + } + } + + public Byte getCredentialStatus(UUID userId, Byte serviceId) { + ResponseEntity response = restTemplate.exchange( + coreUrl + "/credentials/{service_id}/{user_id}/status", + HttpMethod.GET, + null, + Byte.class, + serviceId, userId + ); + + if (!response.getStatusCode().is2xxSuccessful()) { + handleError(response); + } + + return response.getBody(); + } + + public void updateCredentialStatus(UUID userId, Byte serviceId, Byte newStatus) { + ChangeStatusRequest req = new ChangeStatusRequest(newStatus); + HttpEntity requestEntity = new HttpEntity<>(req); + + ResponseEntity response = restTemplate.exchange( + coreUrl + "/credentials/{service_id}/{user_id}/status", + HttpMethod.PUT, + requestEntity, + Void.class, + serviceId, userId + ); + + if (!response.getStatusCode().is2xxSuccessful()) { + handleError(response); + } + } + + private void handleError(ResponseEntity response) { + HttpStatusCode statusCode = response.getStatusCode(); + + if (statusCode.equals(HttpStatus.UNAUTHORIZED)) { + throw new UnauthorizedException("Credenciales no válidas"); + } else if (statusCode.equals(HttpStatus.FORBIDDEN)) { + throw new ForbiddenException("Esa cuenta está desactivada"); + } else if (statusCode.equals(HttpStatus.NOT_FOUND)) { + throw new NotFoundException("No encontrado"); + } else if (statusCode.equals(HttpStatus.BAD_REQUEST)) { + throw new BadRequestException("Datos de solicitud faltantes"); + } else if (statusCode.equals(HttpStatus.CONFLICT)) { + throw new ConflictException("Ya existe"); + } else if (statusCode.equals(HttpStatus.UNPROCESSABLE_CONTENT)) { + throw new ValidationException("general", "Los datos no tienen formato válido"); + } else { + if (statusCode.is4xxClientError()) { + throw new BadRequestException(response.getBody().toString()); + } else { + throw new RuntimeException("Error desconocido"); + } + } + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/config/CacheConfig.java b/huertos/src/main/java/net/miarma/backend/huertos/config/CacheConfig.java new file mode 100644 index 0000000..07c1278 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/config/CacheConfig.java @@ -0,0 +1,26 @@ +package net.miarma.backend.huertos.config; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.TimeUnit; + +@Configuration +public class CacheConfig { + @Bean + public CacheManager cacheManager() { + CaffeineCacheManager manager = new CaffeineCacheManager( + "members", "memberById", "waitlist", + "metadataByUserId", "metadataByMemberNumber", "metadataExists"); + manager.setCaffeine( + Caffeine.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .maximumSize(10_000) + ); + return manager; + } + +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/config/RestTemplateConfig.java b/huertos/src/main/java/net/miarma/backend/huertos/config/RestTemplateConfig.java new file mode 100644 index 0000000..73bbbea --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/config/RestTemplateConfig.java @@ -0,0 +1,48 @@ +package net.miarma.backend.huertos.config; + +import io.jsonwebtoken.io.IOException; +import net.miarma.backend.huertos.service.CoreAuthService; +import net.miarma.backlib.security.CoreAuthTokenHolder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate authRestTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setErrorHandler(new NoOpResponseErrorHandler()); + return restTemplate; + } + + @Bean + public RestTemplate secureRestTemplate(CoreAuthService coreAuthService) { + RestTemplate restTemplate = new RestTemplate(); + + restTemplate.getInterceptors().add((request, body, execution) -> { + String token = coreAuthService.getToken(); + request.getHeaders().setBearerAuth(token); + return execution.execute(request, body); + }); + + restTemplate.setErrorHandler(new NoOpResponseErrorHandler()); + + return restTemplate; + } + + public static class NoOpResponseErrorHandler implements ResponseErrorHandler { + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + return false; + } + } +} + diff --git a/huertos/src/main/java/net/miarma/backend/huertos/config/SecurityConfig.java b/huertos/src/main/java/net/miarma/backend/huertos/config/SecurityConfig.java new file mode 100644 index 0000000..2b3f704 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/config/SecurityConfig.java @@ -0,0 +1,70 @@ +package net.miarma.backend.huertos.config; + +import net.miarma.backend.huertos.security.HuertosJwtFilter; +import net.miarma.backlib.http.RestAccessDeniedHandler; +import net.miarma.backlib.http.RestAuthEntryPoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfigurationSource; + +import java.util.Optional; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) +public class SecurityConfig { + + private final HuertosJwtFilter jwtFilter; + private final RestAuthEntryPoint authEntryPoint; + private final RestAccessDeniedHandler accessDeniedHandler; + private final CorsConfigurationSource corsConfigurationSource; + + public SecurityConfig( + HuertosJwtFilter jwtFilter, + RestAuthEntryPoint authEntryPoint, + RestAccessDeniedHandler accessDeniedHandler, + Optional corsConfigurationSource + ) { + this.jwtFilter = jwtFilter; + this.authEntryPoint = authEntryPoint; + this.accessDeniedHandler = accessDeniedHandler; + this.corsConfigurationSource = corsConfigurationSource.orElse(null); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + if (corsConfigurationSource != null) { + http.cors(Customizer.withDefaults()); + } + + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(authEntryPoint) + .accessDeniedHandler(accessDeniedHandler) + ) + .authorizeHttpRequests(auth -> auth + // PUBLICAS + .requestMatchers("/auth/login").permitAll() + .requestMatchers("/users/waitlist/limited").permitAll() + .requestMatchers("/requests").permitAll() + .requestMatchers("/users/latest-number").permitAll() + // PRIVADAS + .anyRequest().authenticated() + ); + + http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} + + diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/AnnouncementController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/AnnouncementController.java new file mode 100644 index 0000000..2c183da --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/AnnouncementController.java @@ -0,0 +1,69 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.dto.AnnouncementDto; +import net.miarma.backend.huertos.mapper.AnnouncementMapper; +import net.miarma.backend.huertos.model.Announcement; +import net.miarma.backend.huertos.service.AnnouncementService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping("/announcements") +public class AnnouncementController { + + private final AnnouncementService announcementService; + + public AnnouncementController(AnnouncementService announcementService) { + this.announcementService = announcementService; + } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok( + announcementService.getAll() + .stream() + .map(AnnouncementMapper::toResponse) + .toList() + ); + } + + @GetMapping("/{announce_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getById(@PathVariable("announce_id") UUID announcementId) { + Announcement announcement = announcementService.getById(announcementId); + return ResponseEntity.ok(AnnouncementMapper.toResponse(announcement)); + } + + @PutMapping("/{announce_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity update( + @PathVariable("announce_id") UUID announcementId, + @RequestBody AnnouncementDto.Request dto + ) { + return ResponseEntity.ok( + AnnouncementMapper.toResponse(announcementService.update(announcementId, AnnouncementMapper.toEntity(dto))) + ); + } + + @PostMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity create(@RequestBody AnnouncementDto.Request dto) { + return ResponseEntity.ok( + AnnouncementMapper.toResponse( + announcementService.create( + AnnouncementMapper.toEntity(dto) + ))); + } + + @DeleteMapping("/{announce_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> delete(@PathVariable("announce_id") UUID announcementId) { + announcementService.delete(announcementId); + return ResponseEntity.ok(Map.of("message", "Deleted announcement: " + announcementId)); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/BalanceController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/BalanceController.java new file mode 100644 index 0000000..dbe20d6 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/BalanceController.java @@ -0,0 +1,47 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.dto.BalanceDto; +import net.miarma.backend.huertos.dto.view.VBalanceWithTotalsDto; +import net.miarma.backend.huertos.mapper.BalanceMapper; +import net.miarma.backend.huertos.mapper.view.VBalanceWithTotalsMapper; +import net.miarma.backend.huertos.model.Balance; +import net.miarma.backend.huertos.service.BalanceService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/balance") +public class BalanceController { + private final BalanceService balanceService; + + public BalanceController(BalanceService balanceService) { + this.balanceService = balanceService; + } + + @GetMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getBalance() { + Balance balance = balanceService.get(); + return ResponseEntity.ok(BalanceMapper.toDto(balance)); + } + + @GetMapping("/with-totals") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getWithTotals() { + return ResponseEntity.ok(VBalanceWithTotalsMapper.toDto(balanceService.getWithTotals())); + } + + @PostMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity setBalance(BalanceDto dto) { + return ResponseEntity.ok( + BalanceMapper.toDto( + balanceService.create(BalanceMapper.toEntity(dto)) + ) + ); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/ExpenseController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/ExpenseController.java new file mode 100644 index 0000000..4aab85c --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/ExpenseController.java @@ -0,0 +1,74 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.dto.ExpenseDto; +import net.miarma.backend.huertos.mapper.ExpenseMapper; +import net.miarma.backend.huertos.model.Expense; +import net.miarma.backend.huertos.service.ExpenseService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping("/expenses") +public class ExpenseController { + private ExpenseService expenseService; + + public ExpenseController(ExpenseService expenseService) { + this.expenseService = expenseService; + } + + @GetMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getAll() { + return ResponseEntity.ok( + expenseService.getAll() + .stream() + .map(ExpenseMapper::toResponse) + .toList() + ); + } + + @PostMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity create(@RequestBody ExpenseDto.Request dto) { + return ResponseEntity.ok( + ExpenseMapper.toResponse( + expenseService.create( + ExpenseMapper.toEntity(dto) + ))); + } + + @GetMapping("/{expense_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getById(@PathVariable("expense_id") UUID expenseId) { + Expense expense = expenseService.getById(expenseId); + return ResponseEntity.ok(ExpenseMapper.toResponse(expense)); + } + + @PutMapping("/{expense_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity update( + @PathVariable("expense_id") UUID expenseId, + @RequestBody ExpenseDto.Request dto + ) { + Expense updated = expenseService.update( + expenseId, + ExpenseMapper.toEntity(dto) + ); + + return ResponseEntity.ok( + ExpenseMapper.toResponse(updated) + ); + } + + @DeleteMapping("/{expense_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> delete(@PathVariable("expense_id") UUID expenseId) { + expenseService.delete(expenseId); + return ResponseEntity.ok(Map.of("message", "Deleted expense: " + expenseId)); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/HuertosAuthController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/HuertosAuthController.java new file mode 100644 index 0000000..2b260ff --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/HuertosAuthController.java @@ -0,0 +1,41 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.client.CoreAuthClient; +import net.miarma.backend.huertos.dto.HuertosLoginResponse; +import net.miarma.backend.huertos.mapper.UserMetadataMapper; +import net.miarma.backend.huertos.model.UserMetadata; +import net.miarma.backend.huertos.service.UserMetadataService; +import net.miarma.backlib.dto.LoginRequest; +import net.miarma.backlib.dto.LoginResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/auth") +public class HuertosAuthController { + private final UserMetadataService metadataService; + private final CoreAuthClient authClient; + + public HuertosAuthController(UserMetadataService metadataService, + CoreAuthClient authClient) { + this.metadataService = metadataService; + this.authClient = authClient; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest req) { + LoginResponse coreResponse = authClient.login(req); + UserMetadata metadata = metadataService.getById(coreResponse.user().getUserId()); + return ResponseEntity.ok( + new HuertosLoginResponse( + coreResponse.token(), + coreResponse.user(), + coreResponse.account(), + UserMetadataMapper.toDto(metadata) + ) + ); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/IncomeController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/IncomeController.java new file mode 100644 index 0000000..26794a5 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/IncomeController.java @@ -0,0 +1,114 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.dto.IncomeDto; +import net.miarma.backend.huertos.dto.view.VIncomesWithInfoDto; +import net.miarma.backend.huertos.mapper.IncomeMapper; +import net.miarma.backend.huertos.mapper.view.VIncomesWithInfoMapper; +import net.miarma.backend.huertos.model.Income; +import net.miarma.backend.huertos.service.IncomeService; +import net.miarma.backend.huertos.service.view.VIncomesWithInfoService; +import net.miarma.backlib.security.JwtService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping("/incomes") +public class IncomeController { + private IncomeService incomeService; + private VIncomesWithInfoService vIncomesWithInfoService; + private JwtService jwtService; + + public IncomeController(IncomeService incomeService, VIncomesWithInfoService vIncomesWithInfoService, JwtService jwtService) { + this.incomeService = incomeService; + this.vIncomesWithInfoService = vIncomesWithInfoService; + this.jwtService = jwtService; + } + + @GetMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getAll() { + return ResponseEntity.ok( + incomeService.getAll() + .stream() + .map(IncomeMapper::toResponse) + .toList() + ); + } + + @GetMapping("/with-info") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getAllWithInfo() { + return ResponseEntity.ok( + vIncomesWithInfoService.getAll() + .stream() + .map(VIncomesWithInfoMapper::toResponse) + .toList() + ); + } + + @GetMapping("/mine") + public ResponseEntity> getMine(@RequestHeader("Authorization") String authHeader) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + String token = authHeader.substring(7); + + UUID userId; + try { + userId = jwtService.getUserId(token); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + return ResponseEntity.ok( + incomeService.getByUserId(userId) + .stream() + .map(IncomeMapper::toResponse) + .toList() + ); + } + + @PostMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity create(@RequestBody IncomeDto.Request dto) { + return ResponseEntity.ok( + IncomeMapper.toResponse( + incomeService.create( + IncomeMapper.toEntity(dto) + ))); + } + + @GetMapping("/{income_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getById(@PathVariable("income_id") UUID incomeId) { + Income income = incomeService.getById(incomeId); + return ResponseEntity.ok(IncomeMapper.toResponse(income)); + } + + @PutMapping("/{income_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity update( + @PathVariable("income_id") UUID incomeId, + @RequestBody IncomeDto.Request dto + ) { + IO.println(dto.getCreatedAt()); + return ResponseEntity.ok( + IncomeMapper.toResponse( + incomeService.update( + incomeId, IncomeMapper.toEntity(dto)))); + } + + @DeleteMapping("/{income_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> delete(@PathVariable("income_id") UUID incomeId) { + incomeService.delete(incomeId); + return ResponseEntity.ok(Map.of("message", "Deleted income: " + incomeId)); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/MemberController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/MemberController.java new file mode 100644 index 0000000..4a48c92 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/MemberController.java @@ -0,0 +1,125 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.dto.*; +import net.miarma.backend.huertos.dto.view.VIncomesWithInfoDto; +import net.miarma.backend.huertos.mapper.IncomeMapper; +import net.miarma.backend.huertos.mapper.RequestMapper; +import net.miarma.backend.huertos.security.HuertosPrincipal; +import net.miarma.backend.huertos.service.IncomeService; +import net.miarma.backend.huertos.service.MemberService; +import net.miarma.backend.huertos.service.RequestService; +import net.miarma.backlib.security.JwtService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/users") +public class MemberController { + + private final MemberService memberService; + + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + + @GetMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getAll() { + return ResponseEntity.ok(memberService.getAll()); + } + + @GetMapping("/me") + public ResponseEntity getMe(Authentication authentication) { + if (!(authentication.getPrincipal() instanceof HuertosPrincipal principal)) { + throw new IllegalStateException("Tipo de autenticación inválida"); + } + + return ResponseEntity.ok( + memberService.getMyProfile(principal.getUserId()) + ); + } + + @GetMapping("/dropdown") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getDropdown() { + return ResponseEntity.ok(memberService.getDropdown()); + } + + @GetMapping("/{user_id:[0-9a-fA-F\\-]{36}}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getById(@PathVariable("user_id") UUID userId) { + return ResponseEntity.ok(memberService.getById(userId)); + } + + @GetMapping("/latest-number") + public ResponseEntity getLatestNumber() { + return ResponseEntity.ok(memberService.getLatestMemberNumber()); + } + + @GetMapping("/waitlist") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getWaitlist() { + return ResponseEntity.ok(memberService.getWaitlist()); + } + + @GetMapping("/waitlist/limited") + public ResponseEntity> getWaitlistLimited() { + return ResponseEntity.ok(memberService.getWaitlistLimited()); + } + + @GetMapping("/number/{member_number}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getByMemberNumber(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.getByMemberNumber(memberNumber)); + } + + @GetMapping("/number/{member_number}/incomes") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getMemberIncomes(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.getIncomes(memberNumber)); + } + + @GetMapping("/number/{member_number}/has-paid") + public ResponseEntity getMemberHasPaid(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.hasPaid(memberNumber)); + } + + @GetMapping("/number/{member_number}/has-collaborator") + public ResponseEntity getMemberHasCollaborator(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.hasCollaborator(memberNumber)); + } + + @GetMapping("/number/{member_number}/has-greenhouse") + public ResponseEntity getMemberHasGreenhouse(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.hasGreenhouse(memberNumber)); + } + + @GetMapping("/number/{member_number}/has-collaborator-request") + public ResponseEntity getMemberHasCollaboratorRequest(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.hasCollaboratorRequest(memberNumber)); + } + + @GetMapping("/number/{member_number}/has-greenhouse-request") + public ResponseEntity getMemberHasGreenhouseRequest(@PathVariable("member_number") Integer memberNumber) { + return ResponseEntity.ok(memberService.hasGreenhouseRequest(memberNumber)); + } + + @GetMapping("/plot/{plot_number}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getByPlotNumber(@PathVariable("plot_number") Integer plotNumber) { + return ResponseEntity.ok(memberService.getByPlotNumber(plotNumber)); + } + + @GetMapping("/dni/{dni}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getByDni(@PathVariable("dni") String dni) { + return ResponseEntity.ok(memberService.getByDni(dni)); + } +} \ No newline at end of file diff --git a/huertos/src/main/java/net/miarma/backend/huertos/controller/RequestController.java b/huertos/src/main/java/net/miarma/backend/huertos/controller/RequestController.java new file mode 100644 index 0000000..a438f44 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/RequestController.java @@ -0,0 +1,152 @@ +package net.miarma.backend.huertos.controller; + +import net.miarma.backend.huertos.dto.*; +import net.miarma.backend.huertos.mapper.RequestMapper; +import net.miarma.backend.huertos.mapper.RequestMetadataMapper; +import net.miarma.backend.huertos.mapper.RequestWithMetadataMapper; +import net.miarma.backend.huertos.model.Request; +import net.miarma.backend.huertos.service.RequestAcceptanceService; +import net.miarma.backend.huertos.service.RequestService; +import net.miarma.backlib.security.JwtService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/requests") +public class RequestController { + + private final RequestService requestService; + private final RequestAcceptanceService requestAcceptanceService; + private final JwtService jwtService; + + public RequestController(RequestService requestService, + RequestAcceptanceService requestAcceptanceService, + JwtService jwtService) { + this.requestService = requestService; + this.requestAcceptanceService = requestAcceptanceService; + this.jwtService = jwtService; + } + + @GetMapping + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getAll() { + return ResponseEntity.ok( + requestService.getAll() + .stream() + .map(RequestMapper::toResponse) + .toList() + ); + } + + @PostMapping + public ResponseEntity create(@RequestBody RequestDto.Request dto) { + return ResponseEntity.ok( + RequestMapper.toResponse( + requestService.create( + RequestMapper.toEntity(dto) + ))); + } + + @GetMapping("/count") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getRequestCount() { + return ResponseEntity.ok( + requestService.getAll() + .stream() + .map(RequestMapper::toResponse) + .collect(Collectors.collectingAndThen( + Collectors.counting(), + RequestCountDto::new + )) + ); + } + + @GetMapping("/mine") + public ResponseEntity> getMine(@RequestHeader("Authorization") String authHeader) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + String token = authHeader.substring(7); + + UUID userId; + try { + userId = jwtService.getUserId(token); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + return ResponseEntity.ok( + requestService.getByUserId(userId) + .stream() + .map(RequestMapper::toResponse) + .toList() + ); + } + + @GetMapping("/full") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> getAllWithMetadata() { + return ResponseEntity.ok( + requestService.getAll() + .stream() + .map(RequestWithMetadataMapper::toDto) + .toList() + ); + } + + @GetMapping("/full/{request_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getByIdWithMetadata( + @PathVariable("request_id") UUID requestId) { + Request request = requestService.getById(requestId); + return ResponseEntity.ok(RequestWithMetadataMapper.toDto(request)); + } + + @GetMapping("/{request_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity getById(@PathVariable("request_id") UUID requestId) { + Request request = requestService.getById(requestId); + return ResponseEntity.ok(RequestMapper.toResponse(request)); + } + + @PutMapping("/{request_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity update( + @PathVariable("request_id") UUID requestId, + @RequestBody RequestDto.Request dto + ) { + return ResponseEntity.ok( + RequestMapper.toResponse(requestService.update(requestId, RequestMapper.toEntity(dto))) + ); + } + + @PutMapping("/{request_id}/accept") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> acceptRequest(@PathVariable("request_id") UUID requestId) { + Request r = requestAcceptanceService.acceptRequest(requestId); + requestAcceptanceService.handleSideEffects(r); + return ResponseEntity.ok(Map.of("message", "Accepted request: " + r.getRequestId())); + } + + @PutMapping("/{request_id}/reject") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> rejectRequest(@PathVariable("request_id") UUID requestId) { + Request r = requestService.reject(requestId); + return ResponseEntity.ok(Map.of("message", "Denied request: " + r.getRequestId())); + } + + @DeleteMapping("/{request_id}") + @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") + public ResponseEntity> delete(@PathVariable("request_id") UUID requestId) { + requestService.delete(requestId); + return ResponseEntity.ok(Map.of("message", "Deleted request: " + requestId)); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/AnnouncementDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/AnnouncementDto.java new file mode 100644 index 0000000..85e48bf --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/AnnouncementDto.java @@ -0,0 +1,89 @@ +package net.miarma.backend.huertos.dto; + +import java.time.Instant; +import java.util.UUID; + +public class AnnouncementDto { + public static class Request { + private String body; + private Byte priority; + private UUID publishedBy; + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Byte getPriority() { + return priority; + } + + public void setPriority(Byte priority) { + this.priority = priority; + } + + public UUID getPublishedBy() { + return publishedBy; + } + + public void setPublishedBy(UUID publishedBy) { + this.publishedBy = publishedBy; + } + } + + public static class Response { + private UUID announceId; + private String body; + private Byte priority; + private UUID publishedBy; + private String publishedByName; + private Instant createdAt; + + public UUID getAnnounceId() { + return announceId; + } + + public void setAnnounceId(UUID announceId) { + this.announceId = announceId; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Byte getPriority() { + return priority; + } + + public void setPriority(Byte priority) { + this.priority = priority; + } + + public UUID getPublishedBy() { + return publishedBy; + } + + public void setPublishedBy(UUID publishedBy) { + this.publishedBy = publishedBy; + } + + public String getPublishedByName() { return publishedByName; } + + public void setPublishedByName(String publishedByName) { this.publishedByName = publishedByName; } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/BalanceDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/BalanceDto.java new file mode 100644 index 0000000..f13803f --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/BalanceDto.java @@ -0,0 +1,43 @@ +package net.miarma.backend.huertos.dto; + +import java.math.BigDecimal; +import java.time.Instant; + +public class BalanceDto { + private Byte id; + private BigDecimal initialBank; + private BigDecimal initialCash; + private Instant createdAt; + + public Byte getId() { + return id; + } + + public void setId(Byte id) { + this.id = id; + } + + public BigDecimal getInitialBank() { + return initialBank; + } + + public void setInitialBank(BigDecimal initialBank) { + this.initialBank = initialBank; + } + + public BigDecimal getInitialCash() { + return initialCash; + } + + public void setInitialCash(BigDecimal initialCash) { + this.initialCash = initialCash; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/DropdownDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/DropdownDto.java new file mode 100644 index 0000000..3290a5c --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/DropdownDto.java @@ -0,0 +1,6 @@ +package net.miarma.backend.huertos.dto; + +import java.util.UUID; + +public record DropdownDto(UUID userId, Integer memberNumber, String displayName) { +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/ExpenseDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/ExpenseDto.java new file mode 100644 index 0000000..42299b5 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/ExpenseDto.java @@ -0,0 +1,130 @@ +package net.miarma.backend.huertos.dto; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.UUID; + +public class ExpenseDto { + public static class Request { + private String concept; + private BigDecimal amount; + private String supplier; + private String invoice; + private Byte type; + private Instant createdAt; + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } + + public static class Response { + private UUID expenseId; + private String concept; + private BigDecimal amount; + private String supplier; + private String invoice; + private Byte type; + private Instant createdAt; + + public UUID getExpenseId() { + return expenseId; + } + + public void setExpenseId(UUID expenseId) { + this.expenseId = expenseId; + } + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/HuertosLoginResponse.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/HuertosLoginResponse.java new file mode 100644 index 0000000..93e74fe --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/HuertosLoginResponse.java @@ -0,0 +1,12 @@ +package net.miarma.backend.huertos.dto; + +import net.miarma.backlib.dto.CredentialDto; +import net.miarma.backlib.dto.UserDto; + +public record HuertosLoginResponse( + String token, + UserDto user, + CredentialDto account, + UserMetadataDto metadata +) { +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/IncomeDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/IncomeDto.java new file mode 100644 index 0000000..274ec64 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/IncomeDto.java @@ -0,0 +1,139 @@ +package net.miarma.backend.huertos.dto; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.UUID; + +public class IncomeDto { + public static class Request { + private UUID userId; + private Integer memberNumber; + private String concept; + private BigDecimal amount; + private Byte type; + private Byte frequency; + private Instant createdAt; + + public Integer getMemberNumber() { + return memberNumber; + } + + public void setMemberNumber(Integer memberNumber) { + this.memberNumber = memberNumber; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getFrequency() { + return frequency; + } + + public void setFrequency(Byte frequency) { + this.frequency = frequency; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } + + public static class Response { + private UUID incomeId; + private UUID userId; + private String concept; + private BigDecimal amount; + private Byte type; + private Byte frequency; + private Instant createdAt; + + public UUID getIncomeId() { + return incomeId; + } + + public void setIncomeId(UUID incomeId) { + this.incomeId = incomeId; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getFrequency() { + return frequency; + } + + public void setFrequency(Byte frequency) { + this.frequency = frequency; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/MemberDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/MemberDto.java new file mode 100644 index 0000000..4440c5e --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/MemberDto.java @@ -0,0 +1,8 @@ +package net.miarma.backend.huertos.dto; + +import net.miarma.backlib.dto.CredentialDto; +import net.miarma.backlib.dto.UserDto; + +public record MemberDto(UserDto user, + CredentialDto account, + UserMetadataDto metadata) {} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/MemberProfileDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/MemberProfileDto.java new file mode 100644 index 0000000..4717fcf --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/MemberProfileDto.java @@ -0,0 +1,20 @@ +package net.miarma.backend.huertos.dto; + +import net.miarma.backend.huertos.dto.view.VIncomesWithInfoDto; +import net.miarma.backlib.dto.CredentialDto; +import net.miarma.backlib.dto.UserDto; + +import java.util.List; + +public record MemberProfileDto( + UserDto user, + CredentialDto account, + UserMetadataDto metadata, + List requests, + List payments, + boolean hasCollaborator, + boolean hasGreenhouse, + boolean hasCollaboratorRequest, + boolean hasGreenhouseRequest +) { +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestCountDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestCountDto.java new file mode 100644 index 0000000..5c30653 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestCountDto.java @@ -0,0 +1,5 @@ +package net.miarma.backend.huertos.dto; + +public record RequestCountDto(Long count) { + +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestDto.java new file mode 100644 index 0000000..2e4f71d --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestDto.java @@ -0,0 +1,113 @@ +package net.miarma.backend.huertos.dto; + +import jakarta.annotation.Nullable; + +import java.time.Instant; +import java.util.UUID; + +public class RequestDto { + public static class Request { + private Byte type; + private UUID userId; + private String name; + private RequestMetadataDto metadata; + + public Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public RequestMetadataDto getMetadata() { + return metadata; + } + + public void setMetadata(RequestMetadataDto metadata) { + this.metadata = metadata; + } + } + + public static class Response { + private UUID requestId; + private Byte type; + private Byte status; + private UUID userId; + private String name; + private RequestMetadataDto metadata; + private Instant createdAt; + + public UUID getRequestId() { + return requestId; + } + + public void setRequestId(UUID requestId) { + this.requestId = requestId; + } + + public Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getStatus() { + return status; + } + + public void setStatus(Byte status) { + this.status = status; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public RequestMetadataDto getMetadata() { + return metadata; + } + + public void setMetadata(RequestMetadataDto metadata) { + this.metadata = metadata; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestMetadataDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestMetadataDto.java new file mode 100644 index 0000000..b9ed47a --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestMetadataDto.java @@ -0,0 +1,20 @@ +package net.miarma.backend.huertos.dto; + +import java.time.Instant; + +public record RequestMetadataDto( + Long id, + String displayName, + String dni, + String phone, + String email, + String username, + String address, + String zipCode, + String city, + Integer memberNumber, + Integer plotNumber, + Byte type, + Instant createdAt +) {} + diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestWithMetadataDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestWithMetadataDto.java new file mode 100644 index 0000000..6a452e5 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/RequestWithMetadataDto.java @@ -0,0 +1,15 @@ +package net.miarma.backend.huertos.dto; + +import java.time.Instant; +import java.util.UUID; + +public record RequestWithMetadataDto( + UUID requestId, + UUID userId, + String name, + Byte type, + Byte status, + Instant createdAt, + RequestMetadataDto metadata +) {} + diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/UserMetadataDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/UserMetadataDto.java new file mode 100644 index 0000000..9c40b91 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/UserMetadataDto.java @@ -0,0 +1,106 @@ +package net.miarma.backend.huertos.dto; + +import java.time.Instant; +import java.util.UUID; + +public class UserMetadataDto { + private UUID userId; + private Integer memberNumber; + private Integer plotNumber; + private String dni; + private String phone; + private Byte type; + private Byte role; + private String notes; + private Instant createdAt; + private Instant assignedAt; + private Instant deactivatedAt; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Integer getMemberNumber() { + return memberNumber; + } + + public void setMemberNumber(Integer memberNumber) { + this.memberNumber = memberNumber; + } + + public Integer getPlotNumber() { + return plotNumber; + } + + public void setPlotNumber(Integer plotNumber) { + this.plotNumber = plotNumber; + } + + public String getDni() { + return dni; + } + + public void setDni(String dni) { + this.dni = dni; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getRole() { + return role; + } + + public void setRole(Byte role) { + this.role = role; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getAssignedAt() { + return assignedAt; + } + + public void setAssignedAt(Instant assignedAt) { + this.assignedAt = assignedAt; + } + + public Instant getDeactivatedAt() { + return deactivatedAt; + } + + public void setDeactivatedAt(Instant deactivatedAt) { + this.deactivatedAt = deactivatedAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/WaitlistCensoredDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/WaitlistCensoredDto.java new file mode 100644 index 0000000..c1de35b --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/WaitlistCensoredDto.java @@ -0,0 +1,13 @@ +package net.miarma.backend.huertos.dto; + +public class WaitlistCensoredDto { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/view/VBalanceWithTotalsDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/view/VBalanceWithTotalsDto.java new file mode 100644 index 0000000..1cd6dbb --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/view/VBalanceWithTotalsDto.java @@ -0,0 +1,79 @@ +package net.miarma.backend.huertos.dto.view; + +import java.math.BigDecimal; +import java.time.Instant; + +public class VBalanceWithTotalsDto { + private Byte id; + private BigDecimal initialBank; + private BigDecimal initialCash; + private BigDecimal totalBankExpenses; + private BigDecimal totalCashExpenses; + private BigDecimal totalBankIncomes; + private BigDecimal totalCashIncomes; + private Instant createdAt; + + public Byte getId() { + return id; + } + + public void setId(Byte id) { + this.id = id; + } + + public BigDecimal getInitialBank() { + return initialBank; + } + + public void setInitialBank(BigDecimal initialBank) { + this.initialBank = initialBank; + } + + public BigDecimal getInitialCash() { + return initialCash; + } + + public void setInitialCash(BigDecimal initialCash) { + this.initialCash = initialCash; + } + + public BigDecimal getTotalBankExpenses() { + return totalBankExpenses; + } + + public void setTotalBankExpenses(BigDecimal totalBankExpenses) { + this.totalBankExpenses = totalBankExpenses; + } + + public BigDecimal getTotalCashExpenses() { + return totalCashExpenses; + } + + public void setTotalCashExpenses(BigDecimal totalCashExpenses) { + this.totalCashExpenses = totalCashExpenses; + } + + public BigDecimal getTotalBankIncomes() { + return totalBankIncomes; + } + + public void setTotalBankIncomes(BigDecimal totalBankIncomes) { + this.totalBankIncomes = totalBankIncomes; + } + + public BigDecimal getTotalCashIncomes() { + return totalCashIncomes; + } + + public void setTotalCashIncomes(BigDecimal totalCashIncomes) { + this.totalCashIncomes = totalCashIncomes; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/dto/view/VIncomesWithInfoDto.java b/huertos/src/main/java/net/miarma/backend/huertos/dto/view/VIncomesWithInfoDto.java new file mode 100644 index 0000000..0c2428f --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/view/VIncomesWithInfoDto.java @@ -0,0 +1,89 @@ +package net.miarma.backend.huertos.dto.view; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.UUID; + +public class VIncomesWithInfoDto { + private UUID incomeId; + private UUID userId; + private String displayName; + private Integer memberNumber; + private String concept; + private BigDecimal amount; + private Byte type; + private Byte frequency; + private Instant createdAt; + + public UUID getIncomeId() { + return incomeId; + } + + public void setIncomeId(UUID incomeId) { + this.incomeId = incomeId; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public String getDisplayName() { + return displayName; + } + + public Integer getMemberNumber() { + return memberNumber; + } + + public void setMemberNumber(Integer memberNumber) { + this.memberNumber = memberNumber; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getFrequency() { + return frequency; + } + + public void setFrequency(Byte frequency) { + this.frequency = frequency; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/AnnouncementMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/AnnouncementMapper.java new file mode 100644 index 0000000..0d3d109 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/AnnouncementMapper.java @@ -0,0 +1,31 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.AnnouncementDto; +import net.miarma.backend.huertos.model.Announcement; + +import java.time.Instant; +import java.util.UUID; + +public class AnnouncementMapper { + + public static AnnouncementDto.Response toResponse(Announcement entity) { + AnnouncementDto.Response dto = new AnnouncementDto.Response(); + dto.setAnnounceId(entity.getAnnounceId()); + dto.setBody(entity.getBody()); + dto.setPriority(entity.getPriority()); + dto.setPublishedBy(entity.getPublishedBy()); + dto.setPublishedByName(entity.getPublishedByName()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } + + public static Announcement toEntity(AnnouncementDto.Request dto) { + Announcement entity = new Announcement(); + entity.setAnnounceId(UUID.randomUUID()); + entity.setBody(dto.getBody()); + entity.setPriority(dto.getPriority()); + entity.setPublishedBy(dto.getPublishedBy()); + entity.setCreatedAt(Instant.now()); + return entity; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/BalanceMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/BalanceMapper.java new file mode 100644 index 0000000..238768c --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/BalanceMapper.java @@ -0,0 +1,29 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.BalanceDto; +import net.miarma.backend.huertos.model.Balance; + +public class BalanceMapper { + + public static BalanceDto toDto(Balance balance) { + if (balance == null) return null; + + BalanceDto dto = new BalanceDto(); + dto.setId(balance.getId()); + dto.setInitialBank(balance.getInitialBank()); + dto.setInitialCash(balance.getInitialCash()); + dto.setCreatedAt(balance.getCreatedAt()); + return dto; + } + + public static Balance toEntity(BalanceDto dto) { + if (dto == null) return null; + + Balance balance = new Balance(); + balance.setId(dto.getId()); + balance.setInitialBank(dto.getInitialBank()); + balance.setInitialCash(dto.getInitialCash()); + balance.setCreatedAt(dto.getCreatedAt()); + return balance; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/DropdownDtoMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/DropdownDtoMapper.java new file mode 100644 index 0000000..944a9c6 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/DropdownDtoMapper.java @@ -0,0 +1,10 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.DropdownDto; +import net.miarma.backend.huertos.dto.MemberDto; + +public class DropdownDtoMapper { + public static DropdownDto toDto(MemberDto dto) { + return new DropdownDto(dto.user().getUserId(), dto.metadata().getMemberNumber(), dto.user().getDisplayName()); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/ExpenseMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/ExpenseMapper.java new file mode 100644 index 0000000..701258c --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/ExpenseMapper.java @@ -0,0 +1,34 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.ExpenseDto; +import net.miarma.backend.huertos.model.Expense; + +public class ExpenseMapper { + + public static ExpenseDto.Response toResponse(Expense entity) { + if (entity == null) return null; + + ExpenseDto.Response dto = new ExpenseDto.Response(); + dto.setExpenseId(entity.getExpenseId()); + dto.setConcept(entity.getConcept()); + dto.setAmount(entity.getAmount()); + dto.setSupplier(entity.getSupplier()); + dto.setInvoice(entity.getInvoice()); + dto.setType(entity.getType()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } + + public static Expense toEntity(ExpenseDto.Request dto) { + if (dto == null) return null; + + Expense entity = new Expense(); + entity.setConcept(dto.getConcept()); + entity.setAmount(dto.getAmount()); + entity.setSupplier(dto.getSupplier()); + entity.setInvoice(dto.getInvoice()); + entity.setType(dto.getType()); + entity.setCreatedAt(dto.getCreatedAt()); + return entity; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/IncomeMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/IncomeMapper.java new file mode 100644 index 0000000..ea40403 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/IncomeMapper.java @@ -0,0 +1,34 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.IncomeDto; +import net.miarma.backend.huertos.model.Income; + +public class IncomeMapper { + + public static IncomeDto.Response toResponse(Income entity) { + if (entity == null) return null; + + IncomeDto.Response dto = new IncomeDto.Response(); + dto.setIncomeId(entity.getIncomeId()); + dto.setUserId(entity.getUserId()); + dto.setConcept(entity.getConcept()); + dto.setAmount(entity.getAmount()); + dto.setType(entity.getType()); + dto.setFrequency(entity.getFrequency()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } + + public static Income toEntity(IncomeDto.Request dto) { + if (dto == null) return null; + + Income entity = new Income(); + entity.setUserId(dto.getUserId()); + entity.setConcept(dto.getConcept()); + entity.setAmount(dto.getAmount()); + entity.setType(dto.getType()); + entity.setFrequency(dto.getFrequency()); + entity.setCreatedAt(dto.getCreatedAt()); + return entity; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestMapper.java new file mode 100644 index 0000000..e7f4a33 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestMapper.java @@ -0,0 +1,44 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.RequestDto; +import net.miarma.backend.huertos.model.Request; + +public class RequestMapper { + + public static RequestDto.Response toResponse(Request entity) { + if (entity == null) return null; + + RequestDto.Response dto = new RequestDto.Response(); + dto.setRequestId(entity.getRequestId()); + dto.setType(entity.getType()); + dto.setStatus(entity.getStatus()); + dto.setUserId(entity.getUserId()); + dto.setCreatedAt(entity.getCreatedAt()); + + if (entity.getMetadata() != null) { + dto.setMetadata( + RequestMetadataMapper.toDto(entity.getMetadata()) + ); + } + + return dto; + } + + public static Request toEntity(RequestDto.Request dto) { + if (dto == null) return null; + + Request entity = new Request(); + entity.setType(dto.getType()); + entity.setUserId(dto.getUserId()); + entity.setName(dto.getName()); + entity.setStatus((byte) 0); + + if (dto.getMetadata() != null) { + entity.setMetadata( + RequestMetadataMapper.fromDto(dto.getMetadata()) + ); + } + + return entity; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestMetadataMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestMetadataMapper.java new file mode 100644 index 0000000..3055cf3 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestMetadataMapper.java @@ -0,0 +1,50 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.RequestMetadataDto; +import net.miarma.backend.huertos.model.RequestMetadata; + +public class RequestMetadataMapper { + + public static RequestMetadata fromDto(RequestMetadataDto dto) { + if (dto == null) return null; + + RequestMetadata metadata = new RequestMetadata(); + + metadata.setDisplayName(dto.displayName()); + metadata.setDni(dto.dni()); + metadata.setPhone(dto.phone()); + metadata.setEmail(dto.email()); + + metadata.setUsername(dto.username()); + metadata.setAddress(dto.address()); + metadata.setZipCode(dto.zipCode()); + metadata.setCity(dto.city()); + + metadata.setMemberNumber(dto.memberNumber()); + metadata.setPlotNumber(dto.plotNumber()); + + metadata.setType(dto.type()); + + return metadata; + } + + public static RequestMetadataDto toDto(RequestMetadata entity) { + if (entity == null) return null; + + return new RequestMetadataDto( + entity.getId(), + entity.getDisplayName(), + entity.getDni(), + entity.getPhone(), + entity.getEmail(), + entity.getUsername(), + entity.getAddress(), + entity.getZipCode(), + entity.getCity(), + entity.getMemberNumber(), + entity.getPlotNumber(), + entity.getType(), + entity.getCreatedAt() + ); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestWithMetadataMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestWithMetadataMapper.java new file mode 100644 index 0000000..ab35d78 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/RequestWithMetadataMapper.java @@ -0,0 +1,19 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.RequestWithMetadataDto; +import net.miarma.backend.huertos.model.Request; + +public class RequestWithMetadataMapper { + public static RequestWithMetadataDto toDto(Request r) { + if (r == null) return null; + return new RequestWithMetadataDto( + r.getRequestId(), + r.getUserId(), + r.getName(), + r.getType(), + r.getStatus(), + r.getCreatedAt(), + RequestMetadataMapper.toDto(r.getMetadata()) + ); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/UserMetadataMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/UserMetadataMapper.java new file mode 100644 index 0000000..5aad58c --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/UserMetadataMapper.java @@ -0,0 +1,39 @@ +package net.miarma.backend.huertos.mapper; + +import net.miarma.backend.huertos.dto.UserMetadataDto; +import net.miarma.backend.huertos.model.UserMetadata; + +public class UserMetadataMapper { + + public static UserMetadataDto toDto(UserMetadata entity) { + UserMetadataDto dto = new UserMetadataDto(); + dto.setUserId(entity.getUserId()); + dto.setMemberNumber(entity.getMemberNumber()); + dto.setPlotNumber(entity.getPlotNumber()); + dto.setDni(entity.getDni()); + dto.setPhone(entity.getPhone()); + dto.setType(entity.getType()); + dto.setRole(entity.getRole()); + dto.setNotes(entity.getNotes()); + dto.setCreatedAt(entity.getCreatedAt()); + dto.setAssignedAt(entity.getAssignedAt()); + dto.setDeactivatedAt(entity.getDeactivatedAt()); + return dto; + } + + public static UserMetadata fromDto(UserMetadataDto dto) { + UserMetadata entity = new UserMetadata(); + entity.setUserId(dto.getUserId()); + entity.setMemberNumber(dto.getMemberNumber()); + entity.setPlotNumber(dto.getPlotNumber()); + entity.setDni(dto.getDni()); + entity.setPhone(dto.getPhone()); + entity.setType(dto.getType()); + entity.setRole(dto.getRole()); + entity.setNotes(dto.getNotes()); + entity.setCreatedAt(dto.getCreatedAt()); + entity.setAssignedAt(dto.getAssignedAt()); + entity.setDeactivatedAt(dto.getDeactivatedAt()); + return entity; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/view/VBalanceWithTotalsMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/view/VBalanceWithTotalsMapper.java new file mode 100644 index 0000000..3d07229 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/view/VBalanceWithTotalsMapper.java @@ -0,0 +1,20 @@ +package net.miarma.backend.huertos.mapper.view; + +import net.miarma.backend.huertos.dto.view.VBalanceWithTotalsDto; +import net.miarma.backend.huertos.model.view.VBalanceWithTotals; + +public class VBalanceWithTotalsMapper { + + public static VBalanceWithTotalsDto toDto(VBalanceWithTotals entity) { + VBalanceWithTotalsDto dto = new VBalanceWithTotalsDto(); + dto.setId(entity.getId()); + dto.setInitialBank(entity.getInitialBank()); + dto.setInitialCash(entity.getInitialCash()); + dto.setTotalBankExpenses(entity.getTotalBankExpenses()); + dto.setTotalCashExpenses(entity.getTotalCashExpenses()); + dto.setTotalBankIncomes(entity.getTotalBankIncomes()); + dto.setTotalCashIncomes(entity.getTotalCashIncomes()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/mapper/view/VIncomesWithInfoMapper.java b/huertos/src/main/java/net/miarma/backend/huertos/mapper/view/VIncomesWithInfoMapper.java new file mode 100644 index 0000000..b0f6da7 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/view/VIncomesWithInfoMapper.java @@ -0,0 +1,21 @@ +package net.miarma.backend.huertos.mapper.view; + +import net.miarma.backend.huertos.dto.view.VIncomesWithInfoDto; +import net.miarma.backend.huertos.model.view.VIncomesWithInfo; + +public class VIncomesWithInfoMapper { + + public static VIncomesWithInfoDto toResponse(VIncomesWithInfo entity) { + VIncomesWithInfoDto dto = new VIncomesWithInfoDto(); + dto.setIncomeId(entity.getIncomeId()); + dto.setUserId(entity.getUserId()); + dto.setDisplayName(entity.getDisplayName()); + dto.setMemberNumber(entity.getMemberNumber()); + dto.setConcept(entity.getConcept()); + dto.setAmount(entity.getAmount()); + dto.setType(entity.getType()); + dto.setFrequency(entity.getFrequency()); + dto.setCreatedAt(entity.getCreatedAt()); + return dto; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/Announcement.java b/huertos/src/main/java/net/miarma/backend/huertos/model/Announcement.java new file mode 100644 index 0000000..0c2cf54 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/Announcement.java @@ -0,0 +1,108 @@ +package net.miarma.backend.huertos.model; + +import java.time.Instant; +import java.util.UUID; + +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; + +@Entity +@Table(name = "huertos_announces") +public class Announcement { + + @Id + @Column(name = "announce_id", columnDefinition = "BINARY(16)") + private byte[] announceIdBin; + + @Transient + private UUID announceId; + + @Column(name = "body", nullable = false, columnDefinition = "TEXT") + private String body; + + @Column(name = "priority", nullable = false) + private Byte priority; + + @Column(name = "published_by", columnDefinition = "BINARY(16)", nullable = false) + private byte[] publishedByBin; + + @Transient + private UUID publishedBy; + + @Column(name = "published_by_name", nullable = false) + private String publishedByName; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + @PrePersist + @PreUpdate + private void prePersist() { + if (announceId != null) { + announceIdBin = UuidUtil.uuidToBin(announceId); + } + + if (publishedBy != null) { + publishedByBin = UuidUtil.uuidToBin(publishedBy); + } + } + + @PostLoad + private void postLoad() { + if (announceIdBin != null) { + announceId = UuidUtil.binToUUID(announceIdBin); + } + + if (publishedByBin != null) { + publishedBy = UuidUtil.binToUUID(publishedByBin); + } + } + + public UUID getAnnounceId() { + return announceId; + } + + public void setAnnounceId(UUID announceId) { + this.announceId = announceId; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Byte getPriority() { + return priority; + } + + public void setPriority(Byte priority) { + this.priority = priority; + } + + public UUID getPublishedBy() { + return publishedBy; + } + + public void setPublishedBy(UUID publishedBy) { + this.publishedBy = publishedBy; + } + + public String getPublishedByName() { + return publishedByName; + } + + public void setPublishedByName(String publishedByName) { + this.publishedByName = publishedByName; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/Balance.java b/huertos/src/main/java/net/miarma/backend/huertos/model/Balance.java new file mode 100644 index 0000000..1beba9d --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/Balance.java @@ -0,0 +1,58 @@ +package net.miarma.backend.huertos.model; + +import java.math.BigDecimal; +import java.time.Instant; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "huertos_balance") +public class Balance { + + @Id + private Byte id = 1; + + @Column(name = "initial_bank", nullable = false) + private BigDecimal initialBank; + + @Column(name = "initial_cash", nullable = false) + private BigDecimal initialCash; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + public Byte getId() { + return id; + } + + public void setId(Byte id) { + this.id = id; + } + + public BigDecimal getInitialBank() { + return initialBank; + } + + public void setInitialBank(BigDecimal initialBank) { + this.initialBank = initialBank; + } + + public BigDecimal getInitialCash() { + return initialCash; + } + + public void setInitialCash(BigDecimal initialCash) { + this.initialCash = initialCash; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/Expense.java b/huertos/src/main/java/net/miarma/backend/huertos/model/Expense.java new file mode 100644 index 0000000..081734c --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/Expense.java @@ -0,0 +1,109 @@ +package net.miarma.backend.huertos.model; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.UUID; + +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; + +@Entity +@Table(name = "huertos_expenses") +public class Expense { + + @Id + @Column(name = "expense_id", columnDefinition = "BINARY(16)") + private byte[] expenseIdBin; + + @Transient + private UUID expenseId; + + @Column(name = "concept", nullable = false, length = 128) + private String concept; + + @Column(name = "amount", nullable = false) + private BigDecimal amount; + + @Column(name = "supplier", nullable = false, length = 128) + private String supplier; + + @Column(name = "invoice", nullable = false, length = 32) + private String invoice; + + @Column(name = "type") + private Byte type; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + @PrePersist + @PreUpdate + private void prePersist() { + if (expenseId != null) { + expenseIdBin = UuidUtil.uuidToBin(expenseId); + } + } + + @PostLoad + private void postLoad() { + if (expenseIdBin != null) { + expenseId = UuidUtil.binToUUID(expenseIdBin); + } + } + + public UUID getExpenseId() { + return expenseId; + } + + public void setExpenseId(UUID expenseId) { + this.expenseId = expenseId; + } + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/Income.java b/huertos/src/main/java/net/miarma/backend/huertos/model/Income.java new file mode 100644 index 0000000..6ba6da7 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/Income.java @@ -0,0 +1,133 @@ +package net.miarma.backend.huertos.model; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.UUID; + +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; + +@Entity +@Table(name = "huertos_incomes") +public class Income { + + @Id + @Column(name = "income_id", columnDefinition = "BINARY(16)") + private byte[] incomeIdBin; + + @Transient + private UUID incomeId; + + @Column(name = "user_id", columnDefinition = "BINARY(16)", nullable = false) + private byte[] userIdBin; + + @Transient + private UUID userId; + + @Column(name = "concept", nullable = false, length = 128) + private String concept; + + @Column(name = "amount", nullable = false) + private BigDecimal amount; + + @Column(name = "type") + private Byte type; + + @Column(name = "frequency") + private Byte frequency; + + @Column(name = "created_at", nullable = false, updatable = true) + private Instant createdAt; + + @PrePersist + @PreUpdate + private void prePersist() { + if (userId != null) { + userIdBin = UuidUtil.uuidToBin(userId); + } + + if (incomeId != null) { + incomeIdBin = UuidUtil.uuidToBin(incomeId); + } + } + + @PostLoad + private void postLoad() { + if (userIdBin != null) { + userId = UuidUtil.binToUUID(userIdBin); + } + + if (incomeIdBin != null) { + incomeId = UuidUtil.binToUUID(incomeIdBin); + } + } + + public boolean isPaid() { + Instant now = Instant.now(); + if (frequency == 0) { // BIYEARLY + return Duration.between(createdAt, now).toDays() <= 6L * 30; + } else if (frequency == 1) { // YEARLY + return Duration.between(createdAt, now).toDays() <= 12L * 30; + } else { + return false; + } + } + + public UUID getIncomeId() { + return incomeId; + } + + public void setIncomeId(UUID incomeId) { + this.incomeId = incomeId; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + 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 Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getFrequency() { + return frequency; + } + + public void setFrequency(Byte frequency) { + this.frequency = frequency; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/Request.java b/huertos/src/main/java/net/miarma/backend/huertos/model/Request.java new file mode 100644 index 0000000..9bc5dcb --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/Request.java @@ -0,0 +1,76 @@ +package net.miarma.backend.huertos.model; + +import java.time.Instant; +import java.util.UUID; +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; + +@Entity +@Table(name = "huertos_requests") +public class Request { + + @Id + @Column(name = "request_id", columnDefinition = "BINARY(16)") + private byte[] requestIdBin; + + @Transient + private UUID requestId; + + @Column(name = "user_id", columnDefinition = "BINARY(16)") + private byte[] userIdBin; + + @Transient + private UUID userId; + + private String name; + + @Column(nullable = false) + private Byte type; + + @Column(nullable = false) + private Byte status; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + @OneToOne(mappedBy = "request", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true) + private RequestMetadata metadata; + + @PrePersist + private void prePersist() { + if (requestId != null) requestIdBin = UuidUtil.uuidToBin(requestId); + if (userId != null) userIdBin = UuidUtil.uuidToBin(userId); + } + + @PostLoad + private void postLoad() { + if (requestIdBin != null) requestId = UuidUtil.binToUUID(requestIdBin); + if (userIdBin != null) userId = UuidUtil.binToUUID(userIdBin); + } + + public UUID getRequestId() { return requestId; } + public void setRequestId(UUID requestId) { this.requestId = requestId; } + + public UUID getUserId() { return userId; } + public void setUserId(UUID userId) { this.userId = userId; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Byte getType() { return type; } + public void setType(Byte type) { this.type = type; } + + public Byte getStatus() { return status; } + public void setStatus(Byte status) { this.status = status; } + + public Instant getCreatedAt() { return createdAt; } + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } + + public RequestMetadata getMetadata() { return metadata; } + public void setMetadata(RequestMetadata metadata) { this.metadata = metadata; } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/RequestMetadata.java b/huertos/src/main/java/net/miarma/backend/huertos/model/RequestMetadata.java new file mode 100644 index 0000000..e3c2d1b --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/RequestMetadata.java @@ -0,0 +1,199 @@ +package net.miarma.backend.huertos.model; + +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; + +import java.time.Instant; +import java.util.UUID; + +@Entity +@Table(name = "huertos_request_metadata") +public class RequestMetadata { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "request_id", columnDefinition = "BINARY(16)", nullable = false, unique = true) + private byte[] requestIdBin; + + @Transient + private UUID requestId; + + @OneToOne + @JoinColumn(name = "request_id", referencedColumnName = "request_id", insertable = false, updatable = false) + private Request request; + + @Column(name = "display_name", nullable = false, length = 150) + private String displayName; + + @Column(nullable = false, length = 20) + private String dni; + + @Column(length = 30) + private String phone; + + @Column(nullable = false, length = 150) + private String email; + + @Column(length = 255) + private String address; + + @Column(name = "zip_code", length = 10) + private String zipCode; + + @Column(length = 100) + private String city; + + @Column(name = "member_number") + private Integer memberNumber; + + @Column(name = "plot_number") + private Integer plotNumber; + + @Column(nullable = false, length = 100) + private String username; + + private Byte type; + + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @PrePersist + private void prePersist() { + if (requestId != null) requestIdBin = UuidUtil.uuidToBin(requestId); + } + + @PostLoad + private void postLoad() { + if (requestIdBin != null) requestId = UuidUtil.binToUUID(requestIdBin); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public byte[] getRequestIdBin() { + return requestIdBin; + } + + public void setRequestIdBin(byte[] requestIdBin) { + this.requestIdBin = requestIdBin; + } + + public UUID getRequestId() { + return requestId; + } + + public void setRequestId(UUID requestId) { + this.requestId = requestId; + } + + public Request getRequest() { + return request; + } + + public void setRequest(Request request) { + this.request = request; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDni() { + return dni; + } + + public void setDni(String dni) { + this.dni = dni; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getZipCode() { + return zipCode; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public Integer getMemberNumber() { + return memberNumber; + } + + public void setMemberNumber(Integer memberNumber) { + this.memberNumber = memberNumber; + } + + public Integer getPlotNumber() { + return plotNumber; + } + + public void setPlotNumber(Integer plotNumber) { + this.plotNumber = plotNumber; + } + + public Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/UserMetadata.java b/huertos/src/main/java/net/miarma/backend/huertos/model/UserMetadata.java new file mode 100644 index 0000000..997bdd4 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/UserMetadata.java @@ -0,0 +1,157 @@ +package net.miarma.backend.huertos.model; + +import java.time.Instant; +import java.util.UUID; + +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; + +@Entity +@Table(name = "huertos_user_metadata") +public class UserMetadata { + + @Id + @Column(name = "user_id", columnDefinition = "BINARY(16)") + private byte[] userIdBin; + + @Transient + private UUID userId; + + @Column(name = "member_number", nullable = false, unique = true) + private Integer memberNumber; + + @Column(name = "plot_number", nullable = false) + private Integer plotNumber; + + @Column(name = "dni", nullable = false, unique = true, length = 9) + private String dni; + + @Column(name = "phone", nullable = false, length = 20) + private String phone; + + @Column(name = "type", nullable = false) + private Byte type; + + @Column(name = "role", nullable = false) + private Byte role; + + @Column(name = "notes") + private String notes; + + @Column(name = "created_at", nullable = false) + private Instant createdAt; + + @Column(name = "assigned_at") + private Instant assignedAt; + + @Column(name = "deactivated_at") + private Instant deactivatedAt; + + @PrePersist + @PreUpdate + private void prePersist() { + if (userId != null) { + userIdBin = UuidUtil.uuidToBin(userId); + } + } + + @PostLoad + private void postLoad() { + if (userIdBin != null) { + userId = UuidUtil.binToUUID(userIdBin); + } + } + + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Integer getMemberNumber() { + return memberNumber; + } + + public void setMemberNumber(Integer memberNumber) { + this.memberNumber = memberNumber; + } + + public Integer getPlotNumber() { + return plotNumber; + } + + public void setPlotNumber(Integer plotNumber) { + this.plotNumber = plotNumber; + } + + public String getDni() { + return dni; + } + + public void setDni(String dni) { + this.dni = dni; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Byte getType() { + return type; + } + + public void setType(Byte type) { + this.type = type; + } + + public Byte getRole() { + return role; + } + + public void setRole(Byte role) { + this.role = role; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getAssignedAt() { + return assignedAt; + } + + public void setAssignedAt(Instant assignedAt) { + this.assignedAt = assignedAt; + } + + public Instant getDeactivatedAt() { + return deactivatedAt; + } + + public void setDeactivatedAt(Instant deactivatedAt) { + this.deactivatedAt = deactivatedAt; + } + + public void setUserIdBin(byte[] userIdBin) { + this.userIdBin = userIdBin; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/view/VBalanceWithTotals.java b/huertos/src/main/java/net/miarma/backend/huertos/model/view/VBalanceWithTotals.java new file mode 100644 index 0000000..0358968 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/view/VBalanceWithTotals.java @@ -0,0 +1,72 @@ +package net.miarma.backend.huertos.model.view; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.Immutable; + +import java.math.BigDecimal; +import java.time.Instant; + +@Entity +@Immutable +@Table(name = "v_balance_with_totals") +public class VBalanceWithTotals { + + @Id + private Byte id; + + @Column(name = "initial_bank") + private BigDecimal initialBank; + + @Column(name = "initial_cash") + private BigDecimal initialCash; + + @Column(name = "total_bank_expenses") + private BigDecimal totalBankExpenses; + + @Column(name = "total_cash_expenses") + private BigDecimal totalCashExpenses; + + @Column(name = "total_bank_incomes") + private BigDecimal totalBankIncomes; + + @Column(name = "total_cash_incomes") + private BigDecimal totalCashIncomes; + + @Column(name = "created_at") + private Instant createdAt; + + public Byte getId() { + return id; + } + + public BigDecimal getInitialBank() { + return initialBank; + } + + public BigDecimal getInitialCash() { + return initialCash; + } + + public BigDecimal getTotalBankExpenses() { + return totalBankExpenses; + } + + public BigDecimal getTotalCashExpenses() { + return totalCashExpenses; + } + + public BigDecimal getTotalBankIncomes() { + return totalBankIncomes; + } + + public BigDecimal getTotalCashIncomes() { + return totalCashIncomes; + } + + public Instant getCreatedAt() { + return createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/model/view/VIncomesWithInfo.java b/huertos/src/main/java/net/miarma/backend/huertos/model/view/VIncomesWithInfo.java new file mode 100644 index 0000000..b1e5761 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/model/view/VIncomesWithInfo.java @@ -0,0 +1,87 @@ +package net.miarma.backend.huertos.model.view; + +import jakarta.persistence.*; +import net.miarma.backlib.util.UuidUtil; +import org.hibernate.annotations.Immutable; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.UUID; + +@Entity +@Immutable +@Table(name = "v_incomes_with_info") +public class VIncomesWithInfo { + + @Id + @Column(name = "income_id", columnDefinition = "BINARY(16)") + private byte[] incomeIdBin; + + @Transient + private UUID incomeId; + + @Column(name = "user_id", columnDefinition = "BINARY(16)") + private byte[] userIdBin; + + @Transient + private UUID userId; + + @Column(name = "display_name") + private String displayName; + + @Column(name = "member_number") + private Integer memberNumber; + + private String concept; + private BigDecimal amount; + private Byte type; + private Byte frequency; + + @Column(name = "created_at") + private Instant createdAt; + + @PostLoad + private void postLoad() { + if (userIdBin != null) { + userId = UuidUtil.binToUUID(userIdBin); + } + + if (incomeIdBin != null) { + incomeId = UuidUtil.binToUUID(incomeIdBin); + } + } + + public UUID getIncomeId() { + return incomeId; + } + + public UUID getUserId() { + return userId; + } + + public String getDisplayName() { + return displayName; + } + + public Integer getMemberNumber() { return memberNumber; } + + public String getConcept() { + return concept; + } + + public BigDecimal getAmount() { + return amount; + } + + public Byte getType() { + return type; + } + + public Byte getFrequency() { + return frequency; + } + + public Instant getCreatedAt() { + return createdAt; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/AnnouncementRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/AnnouncementRepository.java new file mode 100644 index 0000000..7481e64 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/AnnouncementRepository.java @@ -0,0 +1,7 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.Announcement; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AnnouncementRepository extends JpaRepository { +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/BalanceRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/BalanceRepository.java new file mode 100644 index 0000000..bb96378 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/BalanceRepository.java @@ -0,0 +1,8 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.Balance; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BalanceRepository extends JpaRepository { + +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/ExpenseRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/ExpenseRepository.java new file mode 100644 index 0000000..0b6390f --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/ExpenseRepository.java @@ -0,0 +1,6 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.Expense; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExpenseRepository extends JpaRepository {} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/IncomeRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/IncomeRepository.java new file mode 100644 index 0000000..b584ac1 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/IncomeRepository.java @@ -0,0 +1,7 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.Income; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IncomeRepository extends JpaRepository { +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/RequestMetadataRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/RequestMetadataRepository.java new file mode 100644 index 0000000..49ca41f --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/RequestMetadataRepository.java @@ -0,0 +1,7 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.RequestMetadata; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RequestMetadataRepository extends JpaRepository { +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/RequestRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/RequestRepository.java new file mode 100644 index 0000000..bde9669 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/RequestRepository.java @@ -0,0 +1,18 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.Request; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface RequestRepository extends JpaRepository { + @Query(""" + SELECT r FROM Request r + LEFT JOIN FETCH r.metadata + WHERE r.requestIdBin = :id + """) + Optional findByIdWithMetadata(@Param("id") byte[] id); + +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/UserMetadataRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/UserMetadataRepository.java new file mode 100644 index 0000000..ed54479 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/UserMetadataRepository.java @@ -0,0 +1,10 @@ +package net.miarma.backend.huertos.repository; + +import net.miarma.backend.huertos.model.UserMetadata; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserMetadataRepository extends JpaRepository { + Optional findByMemberNumber(Integer memberNumber); +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/view/VBalanceWithTotalsRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/view/VBalanceWithTotalsRepository.java new file mode 100644 index 0000000..232fd82 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/view/VBalanceWithTotalsRepository.java @@ -0,0 +1,12 @@ +package net.miarma.backend.huertos.repository.view; + +import net.miarma.backend.huertos.model.view.VBalanceWithTotals; +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.Optional; + +public interface VBalanceWithTotalsRepository extends Repository { + List findAll(); + Optional findById(Byte id); +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/repository/view/VIncomesWithInfoRepository.java b/huertos/src/main/java/net/miarma/backend/huertos/repository/view/VIncomesWithInfoRepository.java new file mode 100644 index 0000000..c05f91b --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/repository/view/VIncomesWithInfoRepository.java @@ -0,0 +1,12 @@ +package net.miarma.backend.huertos.repository.view; + +import net.miarma.backend.huertos.model.view.VIncomesWithInfo; +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.Optional; + +public interface VIncomesWithInfoRepository extends Repository { + List findAll(); + Optional findById(byte[] incomeId); +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/security/HuertosJwtFilter.java b/huertos/src/main/java/net/miarma/backend/huertos/security/HuertosJwtFilter.java new file mode 100644 index 0000000..0d6fb68 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/security/HuertosJwtFilter.java @@ -0,0 +1,63 @@ +package net.miarma.backend.huertos.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.miarma.backend.huertos.model.UserMetadata; +import net.miarma.backend.huertos.service.UserMetadataService; +import net.miarma.backlib.security.JwtService; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.UUID; + +@Component +public class HuertosJwtFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final UserMetadataService metadataService; + + public HuertosJwtFilter(JwtService jwtService, UserMetadataService metadataService) { + this.jwtService = jwtService; + this.metadataService = metadataService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String token = authHeader.substring(7); + + if (jwtService.validateToken(token)) { + UUID userId = jwtService.getUserId(token); + Byte serviceId = jwtService.getServiceId(token); + + UserMetadata metadata = metadataService.getById(userId); + + if (metadata != null) { + var principal = new HuertosPrincipal( + userId, + metadata.getRole(), + metadata.getType(), + serviceId + ); + + var auth = new UsernamePasswordAuthenticationToken( + principal, null, principal.getAuthorities() + ); + + SecurityContextHolder.getContext().setAuthentication(auth); + } + } + } + + filterChain.doFilter(request, response); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/security/HuertosPrincipal.java b/huertos/src/main/java/net/miarma/backend/huertos/security/HuertosPrincipal.java new file mode 100644 index 0000000..06d3d8a --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/security/HuertosPrincipal.java @@ -0,0 +1,65 @@ +package net.miarma.backend.huertos.security; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +public class HuertosPrincipal implements UserDetails { + + private final UUID userId; + private final Byte role; + private final Byte type; + private final Byte serviceId; + + public HuertosPrincipal(UUID userId, Byte role, Byte type, Byte serviceId) { + this.userId = userId; + this.role = role; + this.type = type; + this.serviceId = serviceId; + } + + public UUID getUserId() { return userId; } + public Byte getHuertosRole() { return role; } + public Byte getHuertosType() { return type; } + public Byte getServiceId() { return serviceId; } + + @Override + public Collection getAuthorities() { + List auth = new ArrayList<>(); + + String roleName = switch(role) { + case 0 -> "USER"; + case 1 -> "ADMIN"; + case 2 -> "DEV"; + default -> "USER"; + }; + + String typeName = switch(type) { + case 0 -> "WAIT_LIST"; + case 1 -> "MEMBER"; + case 2 -> "WITH_GREENHOUSE"; + case 3 -> "COLLABORATOR"; + case 4 -> "SUBSIDY"; + case 5 -> "DEVELOPER"; + default -> "WAIT_LIST"; + }; + + auth.add(new SimpleGrantedAuthority("ROLE_HUERTOS_ROLE_" + roleName)); + auth.add(new SimpleGrantedAuthority("ROLE_HUERTOS_TYPE_" + typeName)); + + return auth; + } + + @Override public String getPassword() { return ""; } + @Override public String getUsername() { return userId.toString(); } + @Override public boolean isAccountNonExpired() { return true; } + @Override public boolean isAccountNonLocked() { return true; } + @Override public boolean isCredentialsNonExpired() { return true; } + @Override public boolean isEnabled() { return true; } +} + diff --git a/huertos/src/main/java/net/miarma/backend/huertos/security/NameCensorer.java b/huertos/src/main/java/net/miarma/backend/huertos/security/NameCensorer.java new file mode 100644 index 0000000..5e144aa --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/security/NameCensorer.java @@ -0,0 +1,36 @@ +package net.miarma.backend.huertos.security; + + +/** + * 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 name) { + if (name == null || name.isBlank()) return ""; + + String[] words = name.trim().split("\\s+"); + + for (int i = 0; i < words.length; i++) { + String word = words[i]; + int len = word.length(); + + if (len > 3) { + words[i] = word.substring(0, 3) + "*".repeat(len - 3); + } else if (len > 0) { + words[i] = word.charAt(0) + "*".repeat(len - 1); + } + } + + String censored = String.join(" ", words); + + if (censored.length() > 16) { + censored = censored.substring(0, 16) + "..."; + } + + return censored; + } +} \ No newline at end of file diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/AnnouncementService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/AnnouncementService.java new file mode 100644 index 0000000..481fdbe --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/AnnouncementService.java @@ -0,0 +1,71 @@ +package net.miarma.backend.huertos.service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.dto.AnnouncementDto; +import net.miarma.backend.huertos.model.Announcement; +import net.miarma.backend.huertos.model.Income; +import net.miarma.backend.huertos.repository.AnnouncementRepository; +import net.miarma.backlib.exception.NotFoundException; +import net.miarma.backlib.util.UuidUtil; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +@Service +@Transactional +public class AnnouncementService { + + private final AnnouncementRepository announcementRepository; + private final MemberService memberService; + + public AnnouncementService(AnnouncementRepository announcementRepository, MemberService memberService) { + this.announcementRepository = announcementRepository; + this.memberService = memberService; + } + + public List getAll() { + return announcementRepository.findAll().stream() + .sorted(Comparator.comparing(Announcement::getCreatedAt).reversed()) + .toList(); + } + + public Announcement getById(UUID announceId) { + byte[] idBytes = UuidUtil.uuidToBin(announceId); + return announcementRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Anuncio no encontrado")); + } + + public Announcement create(Announcement announcement) { + if (announcement.getAnnounceId() == null) { + announcement.setAnnounceId(UUID.randomUUID()); + } + announcement.setPublishedByName(memberService.getById(announcement.getPublishedBy()).user().getDisplayName()); + announcement.setCreatedAt(Instant.now()); + return announcementRepository.save(announcement); + } + + public Announcement update(UUID announceId, Announcement changes) { + Announcement announcement = getById(announceId); + + if (changes.getBody() != null) + announcement.setBody(changes.getBody()); + + if (changes.getPriority() != null) + announcement.setPriority(changes.getPriority()); + + if (changes.getPublishedBy() != null) + announcement.setPublishedBy(changes.getPublishedBy()); + + return announcementRepository.save(announcement); + } + + public void delete(UUID announceId) { + byte[] idBytes = UuidUtil.uuidToBin(announceId); + if (!announcementRepository.existsById(idBytes)) + throw new NotFoundException("Anuncio no encontrado"); + announcementRepository.deleteById(idBytes); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/BalanceService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/BalanceService.java new file mode 100644 index 0000000..b97dd3d --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/BalanceService.java @@ -0,0 +1,62 @@ +package net.miarma.backend.huertos.service; + +import java.time.Instant; + +import net.miarma.backend.huertos.model.view.VBalanceWithTotals; +import net.miarma.backend.huertos.repository.view.VBalanceWithTotalsRepository; +import net.miarma.backlib.exception.ConflictException; +import net.miarma.backlib.exception.NotFoundException; +import org.springframework.stereotype.Service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.model.Balance; +import net.miarma.backend.huertos.repository.BalanceRepository; + +@Service +@Transactional +public class BalanceService { + + private final BalanceRepository balanceRepository; + private final VBalanceWithTotalsRepository vBalanceWithTotalsRepository; + + public BalanceService(BalanceRepository balanceRepository, VBalanceWithTotalsRepository vBalanceWithTotalsRepository) { + this.balanceRepository = balanceRepository; + this.vBalanceWithTotalsRepository = vBalanceWithTotalsRepository; + } + + public Balance get() { + return balanceRepository.findById((byte) 1) + .orElseThrow(() -> new NotFoundException("Balance no encontrado")); + } + + public VBalanceWithTotals getWithTotals() { + return vBalanceWithTotalsRepository.findById((byte) 1) + .orElseThrow(() -> new NotFoundException("Balance no encontrado")); + } + + public Balance create(Balance balance) { + if (balanceRepository.existsById((byte) 1)) { + throw new ConflictException("Ya hay un valor de balance en la base de datos"); + } + balance.setId((byte) 1); + balance.setCreatedAt(Instant.now()); + return balanceRepository.save(balance); + } + + public Balance update(Balance dto) { + Balance balance = balanceRepository.findById((byte) 1) + .orElseThrow(() -> new NotFoundException("Balance no encontrado")); + + if (dto.getInitialBank() != null) balance.setInitialBank(dto.getInitialBank()); + if (dto.getInitialCash() != null) balance.setInitialCash(dto.getInitialCash()); + + return balanceRepository.save(balance); + } + + public void delete() { + if (!balanceRepository.existsById((byte) 1)) { + throw new NotFoundException("Balance no encontrado"); + } + balanceRepository.deleteById((byte) 1); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/CoreAuthService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/CoreAuthService.java new file mode 100644 index 0000000..1079b0e --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/CoreAuthService.java @@ -0,0 +1,60 @@ +package net.miarma.backend.huertos.service; + +import net.miarma.backend.huertos.client.HuertosWebClient; +import net.miarma.backlib.dto.LoginRequest; +import net.miarma.backlib.dto.LoginResponse; +import net.miarma.backlib.security.CoreAuthTokenHolder; +import net.miarma.backlib.security.JwtService; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import tools.jackson.databind.ObjectMapper; + +import java.time.Instant; + +@Service +public class CoreAuthService { + + private final RestTemplate authRestTemplate; + private final CoreAuthTokenHolder tokenHolder; + private final JwtService jwtService; + + @Value("${huertos.user}") + private String username; + + @Value("${huertos.password}") + private String password; + + @Value("${core.url}") + private String coreUrl; + + public CoreAuthService(@Qualifier("authRestTemplate") RestTemplate authRestTemplate, + CoreAuthTokenHolder tokenHolder, + JwtService jwtService) { + this.authRestTemplate = authRestTemplate; + this.tokenHolder = tokenHolder; + this.jwtService = jwtService; + } + + public synchronized String getToken() { + if (tokenHolder.getToken() == null || tokenHolder.isExpired()) { + refreshToken(); + } + return tokenHolder.getToken(); + } + + private void refreshToken() { + var req = new LoginRequest(username, password, (byte) 1); + + LoginResponse resp = authRestTemplate.postForObject( + coreUrl + "/auth/login", + req, + LoginResponse.class + ); + + String token = resp.token(); + Instant exp = jwtService.getExpiration(token).toInstant(); + tokenHolder.setToken(token, exp); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/ExpenseService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/ExpenseService.java new file mode 100644 index 0000000..e653328 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/ExpenseService.java @@ -0,0 +1,96 @@ +package net.miarma.backend.huertos.service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.dto.ExpenseDto; +import net.miarma.backend.huertos.model.Expense; +import net.miarma.backend.huertos.model.Income; +import net.miarma.backend.huertos.repository.ExpenseRepository; +import net.miarma.backlib.exception.NotFoundException; +import net.miarma.backlib.exception.ValidationException; +import net.miarma.backlib.util.UuidUtil; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +@Service +@Transactional +public class ExpenseService { + + private final ExpenseRepository expenseRepository; + + public ExpenseService(ExpenseRepository expenseRepository) { + this.expenseRepository = expenseRepository; + } + + public List getAll() { + return expenseRepository.findAll().stream() + .sorted(Comparator.comparing(Expense::getCreatedAt).reversed()) + .toList(); + } + + public Expense getById(UUID expenseId) { + byte[] idBytes = UuidUtil.uuidToBin(expenseId); + return expenseRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Gasto no encontrado")); + } + + public Expense create(Expense expense) { + if (expense.getConcept() == null || expense.getConcept().isBlank()) { + throw new ValidationException("concept", "El concepto es obligatorio"); + } + if (expense.getAmount() == null) { + throw new ValidationException("amount", "La cantidad es obligatoria"); + } + if (expense.getSupplier() == null || expense.getSupplier().isBlank()) { + throw new ValidationException("supplier", "El proveedor es obligatorio"); + } + if (expense.getInvoice() == null || expense.getInvoice().isBlank()) { + throw new ValidationException("invoice", "La factura es obligatoria"); + } + if (expense.getCreatedAt() == null) { + expense.setCreatedAt(Instant.now()); + } + + expense.setExpenseId(UUID.randomUUID()); + + return expenseRepository.save(expense); + } + + public Expense update(UUID expenseId, Expense changes) { + byte[] idBytes = UuidUtil.uuidToBin(expenseId); + + Expense expense = expenseRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Gasto no encontrado")); + + if (changes.getConcept() != null) + expense.setConcept(changes.getConcept()); + + if (changes.getAmount() != null) + expense.setAmount(changes.getAmount()); + + if (changes.getSupplier() != null) + expense.setSupplier(changes.getSupplier()); + + if (changes.getInvoice() != null) + expense.setInvoice(changes.getInvoice()); + + if (changes.getType() != null) + expense.setType(changes.getType()); + + if (changes.getCreatedAt() != null) + expense.setCreatedAt(changes.getCreatedAt()); + + return expenseRepository.save(expense); + } + + public void delete(UUID expenseId) { + byte[] idBytes = UuidUtil.uuidToBin(expenseId); + if (!expenseRepository.existsById(idBytes)) { + throw new NotFoundException("Gasto no encontrado"); + } + expenseRepository.deleteById(idBytes); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/IncomeService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/IncomeService.java new file mode 100644 index 0000000..91edbf5 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/IncomeService.java @@ -0,0 +1,128 @@ +package net.miarma.backend.huertos.service; + +import java.time.Instant; +import java.time.temporal.TemporalAmount; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +import net.miarma.backend.huertos.model.view.VIncomesWithInfo; +import net.miarma.backend.huertos.service.view.VIncomesWithInfoService; +import net.miarma.backlib.exception.BadRequestException; +import net.miarma.backlib.exception.NotFoundException; +import net.miarma.backlib.exception.ValidationException; +import org.springframework.stereotype.Service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.model.Income; +import net.miarma.backend.huertos.repository.IncomeRepository; +import net.miarma.backlib.util.UuidUtil; + +@Service +@Transactional +public class IncomeService { + + private final IncomeRepository incomeRepository; + private final VIncomesWithInfoService incomesWithInfoService; + private final UserMetadataService metadataService; + + public IncomeService(IncomeRepository incomeRepository, + VIncomesWithInfoService incomesWithInfoService, + UserMetadataService metadataService) { + this.incomeRepository = incomeRepository; + this.incomesWithInfoService = incomesWithInfoService; + this.metadataService = metadataService; + } + + public List getAll() { + return incomeRepository.findAll().stream() + .sorted(Comparator.comparing(Income::getCreatedAt).reversed()) + .toList(); + } + + public Income getById(UUID incomeId) { + byte[] idBytes = UuidUtil.uuidToBin(incomeId); + return incomeRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Ingreso no encontrado")); + } + + public List getByUserId(UUID userId) { + return incomeRepository.findAll().stream() + .filter(i -> i.getUserId().equals(userId)) + .toList(); + } + + public Income create(Income income) { + if (income.getUserId() == null) { + throw new BadRequestException("El identificador de usuario es obligatorio"); + } + if (income.getConcept() == null) { + throw new BadRequestException("El concepto es obligatorio"); + } + if (income.getConcept().isBlank() || income.getConcept().isEmpty()) { + throw new ValidationException("concept", "El concepto no puede ir vacío"); + } + if (income.getAmount() == null || income.getAmount().signum() <= 0) { + throw new ValidationException("amount", "La cantidad debe ser positiva"); + } + if (income.getCreatedAt() == null) { + income.setCreatedAt(Instant.now()); + } + + income.setIncomeId(UUID.randomUUID()); + + return incomeRepository.save(income); + } + + public Income update(UUID incomeId, Income changes) { + byte[] idBytes = UuidUtil.uuidToBin(incomeId); + + Income income = incomeRepository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Ingreso no encontrado")); + + if (changes.getConcept() != null) income.setConcept(changes.getConcept()); + if (changes.getAmount() != null) { + if (changes.getAmount().signum() <= 0) { + throw new ValidationException("amount", "La cantidad debe ser positiva"); + } + income.setAmount(changes.getAmount()); + } + if (changes.getType() != null) income.setType(changes.getType()); + if (changes.getFrequency() != null) income.setFrequency(changes.getFrequency()); + if (changes.getCreatedAt() != null && !changes.getCreatedAt().equals(income.getCreatedAt())) { + income.setCreatedAt(changes.getCreatedAt()); + } + + return incomeRepository.save(income); + } + + public void delete(UUID incomeId) { + byte[] idBytes = UuidUtil.uuidToBin(incomeId); + + if (!incomeRepository.existsById(idBytes)) { + throw new NotFoundException("Ingreso no encontrado"); + } + + incomeRepository.deleteById(idBytes); + } + + public Boolean existsByMemberNumber(Integer memberNumber) { + try { + UUID userId = metadataService.getByMemberNumber(memberNumber).getUserId(); + return !getByUserId(userId).isEmpty(); + } catch (Exception e) { + return false; + } + } + + public Boolean hasPaid(Integer memberNumber) { + UUID userId = metadataService.getByMemberNumber(memberNumber).getUserId(); + List incomes = getByUserId(userId); + return !incomes.isEmpty() && incomes.stream().allMatch(Income::isPaid); + } + + public List getByMemberNumber(Integer memberNumber) { + UUID userId = metadataService.getByMemberNumber(memberNumber).getUserId(); + return incomesWithInfoService.getByUserId(userId); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/MemberService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/MemberService.java new file mode 100644 index 0000000..9f8666e --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/MemberService.java @@ -0,0 +1,202 @@ + package net.miarma.backend.huertos.service; + + import net.miarma.backend.huertos.client.HuertosWebClient; + import net.miarma.backend.huertos.dto.*; + import net.miarma.backend.huertos.dto.view.VIncomesWithInfoDto; + import net.miarma.backend.huertos.mapper.DropdownDtoMapper; + import net.miarma.backend.huertos.mapper.RequestMapper; + import net.miarma.backend.huertos.mapper.UserMetadataMapper; + import net.miarma.backend.huertos.mapper.IncomeMapper; + import net.miarma.backend.huertos.mapper.view.VIncomesWithInfoMapper; + import net.miarma.backend.huertos.security.NameCensorer; + import net.miarma.backlib.dto.UserWithCredentialDto; + import net.miarma.backlib.exception.NotFoundException; + import org.springframework.cache.annotation.Cacheable; + import org.springframework.stereotype.Service; + + import java.util.Comparator; + import java.util.List; + import java.util.UUID; + + @Service + public class MemberService { + private final HuertosWebClient huertosWebClient; + private final IncomeService incomeService; + private final RequestService requestService; + private final UserMetadataService metadataService; + + public MemberService(HuertosWebClient huertosWebClient, + IncomeService incomeService, + RequestService requestService, + UserMetadataService metadataService) { + this.huertosWebClient = huertosWebClient; + this.incomeService = incomeService; + this.requestService = requestService; + this.metadataService = metadataService; + } + + @Cacheable(value = "memberById") + public MemberDto getById(UUID userId) { + var uwc = huertosWebClient.getUserWithCredential(userId, (byte)1); + if (uwc == null) { + throw new NotFoundException("Socio no encontrado"); + } + + var meta = metadataService.getById(userId); + if (meta == null) { + throw new NotFoundException("User metadata not found"); + } + + return new MemberDto( + uwc.user(), + uwc.account(), + UserMetadataMapper.toDto(meta) + ); + } + + @Cacheable("members") + public List getAll() { + List all = huertosWebClient.getAllUsersWithCredentials((byte)1); + + return all.stream() + .filter(uwc -> metadataService.existsById(uwc.user().getUserId())) + .map(uwc -> { + var meta = metadataService.getById(uwc.user().getUserId()); + return new MemberDto( + uwc.user(), + uwc.account(), + UserMetadataMapper.toDto(meta) + ); + }) + .sorted(Comparator.comparing(dto -> dto.metadata().getMemberNumber())) + .toList(); + } + + public MemberProfileDto getMyProfile(UUID userId) { + MemberDto member = getById(userId); + Integer memberNumber = member.metadata().getMemberNumber(); + + List requests = requestService.getByUserId(userId).stream() + .map(RequestMapper::toResponse) + .toList(); + + List payments = incomeService.getByMemberNumber(memberNumber).stream() + .map(VIncomesWithInfoMapper::toResponse) + .toList(); + + return new MemberProfileDto( + member.user(), + member.account(), + member.metadata(), + requests, + payments, + hasCollaborator(memberNumber), + hasGreenhouse(memberNumber), + hasCollaboratorRequest(memberNumber), + hasGreenhouseRequest(memberNumber) + ); + } + + public Integer getLatestMemberNumber() { + return metadataService.getLatestMemberNumber(); + } + + @Cacheable("waitlist") + public List getWaitlist() { + List all = huertosWebClient.getAllUsersWithCredentials((byte)1); + + return all.stream() + .filter(uwc -> metadataService.existsById(uwc.user().getUserId())) + .filter(uwc -> uwc.account().getStatus() != 0) + .map(uwc -> { + var meta = metadataService.getById(uwc.user().getUserId()); + return new MemberDto(uwc.user(), uwc.account(), + UserMetadataMapper.toDto(meta)); + }) + .filter(dto -> dto.metadata().getType().equals((byte) 0)) + .sorted(Comparator.comparing(dto -> dto.metadata().getCreatedAt())) + .toList(); + } + + public List getWaitlistLimited() { + return getWaitlist().stream() + .map(dto -> { + WaitlistCensoredDto censored = new WaitlistCensoredDto(); + censored.setName(NameCensorer.censor(dto.user().getDisplayName())); + return censored; + }) + .toList(); + } + + public MemberDto getByMemberNumber(Integer memberNumber) { + return getAll().stream() + .filter(dto -> dto.metadata().getMemberNumber().equals(memberNumber)) + .findFirst() + .orElseThrow(() -> new NotFoundException("No hay socio con ese número")); + } + + public MemberDto getByPlotNumber(Integer plotNumber) { + return getAll().stream() + .filter(dto -> dto.metadata().getPlotNumber().equals(plotNumber)) + .findFirst() + .orElseThrow(() -> new NotFoundException("No hay socio con ese huerto")); + } + + public MemberDto getByDni(String dni) { + return getAll().stream() + .filter(dto -> dni.equals(dto.metadata().getDni())) + .findFirst() + .orElseThrow(() -> new NotFoundException("No hay socio con ese DNI")); + } + + public List getIncomes(Integer memberNumber) { + return incomeService.getByMemberNumber(memberNumber).stream() + .map(VIncomesWithInfoMapper::toResponse) + .toList(); + } + + public Boolean hasPaid(Integer memberNumber) { + return incomeService.hasPaid(memberNumber); + } + + public Boolean hasCollaborator(Integer memberNumber) { + List all = getAll(); + + var member = all.stream() + .filter(dto -> dto.metadata().getMemberNumber().equals(memberNumber)) + .findFirst() + .orElse(null); + + if (member == null) return false; + + Integer plotNumber = member.metadata().getPlotNumber(); + if (plotNumber == null) return false; + + List plotMembers = all.stream() + .filter(dto -> plotNumber.equals(dto.metadata().getPlotNumber())) + .toList(); + + return plotMembers.stream() + .anyMatch(dto -> dto.metadata().getType().equals((byte)3)); + } + + public Boolean hasGreenhouse(Integer memberNumber) { + return metadataService.getByMemberNumber(memberNumber).getType().equals((byte)2); + } + + public Boolean hasCollaboratorRequest(Integer memberNumber) { + UUID userId = metadataService.getByMemberNumber(memberNumber).getUserId(); + return requestService.hasCollaboratorRequest(userId); + } + + public Boolean hasGreenhouseRequest(Integer memberNumber) { + UUID userId = metadataService.getByMemberNumber(memberNumber).getUserId(); + return requestService.hasGreenhouseRequest(userId); + } + + public List getDropdown() { + return getAll().stream() + .map(DropdownDtoMapper::toDto) + .toList(); + } + } diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/RequestAcceptanceService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/RequestAcceptanceService.java new file mode 100644 index 0000000..50bc655 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/RequestAcceptanceService.java @@ -0,0 +1,140 @@ +package net.miarma.backend.huertos.service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.client.HuertosWebClient; +import net.miarma.backend.huertos.mapper.RequestMetadataMapper; +import net.miarma.backend.huertos.model.Request; +import net.miarma.backend.huertos.model.RequestMetadata; +import net.miarma.backend.huertos.model.UserMetadata; +import net.miarma.backlib.dto.UserWithCredentialDto; +import net.miarma.backlib.exception.BadRequestException; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +public class RequestAcceptanceService { + + private final RequestService requestService; + private final UserMetadataService metadataService; + private final HuertosWebClient huertosWebClient; + private final MemberService memberService; + + public RequestAcceptanceService( + RequestService requestService, + UserMetadataService metadataService, + HuertosWebClient huertosWebClient, + MemberService memberService + ) { + this.requestService = requestService; + this.metadataService = metadataService; + this.huertosWebClient = huertosWebClient; + this.memberService = memberService; + } + + public Request acceptRequest(UUID requestId) { + Request request = requestService.accept(requestId); + + if (request.getMetadata() == null) { + throw new BadRequestException("No hay metadata asociada"); + } + + return request; + } + + public void handleSideEffects(Request request) { + RequestMetadata metadata = request.getMetadata(); + + switch (request.getType()) { + + case 0: // REGISTER + handleRegister(metadata); + break; + + case 1: // UNREGISTER + handleUnregister(metadata); + break; + + case 2: // ADD_COLLABORATOR + handleAddCollaborator(metadata); + break; + + case 3: // REMOVE_COLLABORATOR + handleRemoveCollaborator(metadata); + break; + + case 4: // ADD_GREENHOUSE + handleAddGreenhouse(metadata); + break; + + case 5: // REMOVE_GREENHOUSE + handleRemoveGreenhouse(metadata); + break; + + default: + throw new BadRequestException("Tipo de solicitud no soportado"); + } + } + + private void handleRegister(RequestMetadata metadata) { + UserWithCredentialDto createdUser = + huertosWebClient.createUser(RequestMetadataMapper.toDto(metadata)); + + UserMetadata userMetadata = buildBaseUserMetadata(metadata, createdUser.user().getUserId()); + userMetadata.setType((byte) 0); // socio + userMetadata.setRole((byte) 0); + + metadataService.create(userMetadata); + } + + private void handleUnregister(RequestMetadata metadata) { + UserMetadata toRemove = metadataService.getByMemberNumber(metadata.getMemberNumber()); + huertosWebClient.updateCredentialStatus(toRemove.getUserId(), (byte)1, (byte)0); + } + + private void handleAddCollaborator(RequestMetadata metadata) { + UserWithCredentialDto newCollab = + huertosWebClient.createUser(RequestMetadataMapper.toDto(metadata)); + + UserMetadata collabMeta = buildBaseUserMetadata( + metadata, + newCollab.user().getUserId() + ); + + collabMeta.setType((byte) 3); // colaborador + collabMeta.setRole((byte) 0); + + metadataService.create(collabMeta); + } + + private void handleRemoveCollaborator(RequestMetadata metadata) { + UserMetadata collab = metadataService.getByMemberNumber(metadata.getMemberNumber()); + huertosWebClient.updateCredentialStatus(collab.getUserId(), (byte)1, (byte)0); + } + + private void handleAddGreenhouse(RequestMetadata metadata) { + UserMetadata user = + metadataService.getByMemberNumber(metadata.getMemberNumber()); + + user.setType((byte) 2); // invernadero + metadataService.update(user.getUserId(), user); + } + + private void handleRemoveGreenhouse(RequestMetadata metadata) { + UserMetadata user = + metadataService.getByMemberNumber(metadata.getMemberNumber()); + + user.setType((byte) 1); // socio normal + metadataService.update(user.getUserId(), user); + } + + private UserMetadata buildBaseUserMetadata(RequestMetadata metadata, UUID userId) { + UserMetadata um = new UserMetadata(); + um.setUserId(userId); + um.setMemberNumber(metadata.getMemberNumber()); + um.setPlotNumber(metadata.getPlotNumber()); + um.setDni(metadata.getDni()); + um.setPhone(metadata.getPhone()); + return um; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/RequestService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/RequestService.java new file mode 100644 index 0000000..ae24e52 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/RequestService.java @@ -0,0 +1,140 @@ +package net.miarma.backend.huertos.service; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import net.miarma.backend.huertos.dto.RequestDto; +import net.miarma.backend.huertos.dto.RequestMetadataDto; +import net.miarma.backend.huertos.mapper.RequestMapper; +import net.miarma.backend.huertos.mapper.RequestMetadataMapper; +import net.miarma.backend.huertos.validation.RequestValidator; +import net.miarma.backlib.exception.ConflictException; +import org.springframework.stereotype.Service; +import jakarta.transaction.Transactional; + +import net.miarma.backend.huertos.model.Request; +import net.miarma.backend.huertos.model.RequestMetadata; +import net.miarma.backend.huertos.repository.RequestRepository; +import net.miarma.backend.huertos.repository.RequestMetadataRepository; +import net.miarma.backlib.exception.BadRequestException; +import net.miarma.backlib.exception.NotFoundException; +import net.miarma.backlib.util.UuidUtil; + +@Service +@Transactional +public class RequestService { + + private final RequestRepository requestRepository; + private final RequestMetadataRepository metadataRepository; + + public RequestService(RequestRepository requestRepository, + RequestMetadataRepository metadataRepository) { + this.requestRepository = requestRepository; + this.metadataRepository = metadataRepository; + } + + public List getAll() { + return requestRepository.findAll(); + } + + public Request getById(UUID requestId) { + return requestRepository.findById(UuidUtil.uuidToBin(requestId)) + .orElseThrow(() -> new NotFoundException("Solicitud no encontrada")); + } + + public List getByUserId(UUID userId) { + return requestRepository.findAll().stream() + .filter(r -> r.getUserId() != null && r.getUserId().equals(userId)) + .toList(); + } + + @Transactional + public Request create(Request request) { + + if (request == null) { + throw new BadRequestException("La solicitud es obligatoria"); + } + + if (request.getType() == null) { + throw new BadRequestException("El tipo de solicitud es obligatorio"); + } + + if (request.getType() == 1 && hasUnregisterRequest(request.getUserId())) { + throw new ConflictException("Ya tienes una solicitud, espera que se acepte o se elimine al ser rechazada"); + } + + if ((request.getType() == 2 || request.getType() == 3) && + hasCollaboratorRequest(request.getUserId())) { // tiene soli de collab + throw new ConflictException("Ya tienes una solicitud, espera que se acepte o se elimine al ser rechazada"); + } + + if ((request.getType() == 4 || request.getType() == 5) && + hasGreenhouseRequest(request.getUserId())) { // tiene soli de invernadero + throw new ConflictException("Ya tienes una solicitud, espera que se acepte o se elimine al ser rechazada"); + } + + request.setRequestId(UUID.randomUUID()); + request.setCreatedAt(Instant.now()); + request.getMetadata().setRequestId(request.getRequestId()); + + if (request.getMetadata() != null) { + RequestValidator.validate(request.getMetadata(), request.getType()); + } + + return requestRepository.save(request); + } + + public Request update(UUID requestId, Request changes) { + Request request = getById(requestId); + + if (changes.getType() != null) request.setType(changes.getType()); + if (changes.getStatus() != null) request.setStatus(changes.getStatus()); + if (changes.getUserId() != null) request.setUserId(changes.getUserId()); + + return requestRepository.save(request); + } + + public Request accept(UUID requestId) { + byte[] bin = UuidUtil.uuidToBin(requestId); + Request request = requestRepository.findByIdWithMetadata(bin) + .orElseThrow(() -> new NotFoundException("Request no encontrada")); + if (request.getStatus() != 0) { + throw new BadRequestException("La solicitud ya ha sido procesada"); + } + request.setStatus((byte)1); + return requestRepository.save(request); + } + + public Request reject(UUID requestId) { + Request request = getById(requestId); + if (request.getStatus() != 0) { + throw new BadRequestException("La solicitud ya ha sido procesada"); + } + request.setStatus((byte)2); + return requestRepository.save(request); + } + + public void delete(UUID requestId) { + UUID id = requestId; + if (!requestRepository.existsById(UuidUtil.uuidToBin(id))) { + throw new NotFoundException("Solicitud no encontrada"); + } + requestRepository.deleteById(UuidUtil.uuidToBin(id)); + } + + public boolean hasGreenhouseRequest(UUID userId) { + return getByUserId(userId).stream() + .anyMatch(r -> r.getType() == 4 || r.getType() == 5); + } + + public boolean hasCollaboratorRequest(UUID userId) { + return getByUserId(userId).stream() + .anyMatch(r -> r.getType() == 2 || r.getType() == 3); + } + + public boolean hasUnregisterRequest(UUID userId) { + return getByUserId(userId).stream() + .anyMatch(r -> r.getType() == 1); + } +} \ No newline at end of file diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/UserMetadataService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/UserMetadataService.java new file mode 100644 index 0000000..4b18c93 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/UserMetadataService.java @@ -0,0 +1,120 @@ +package net.miarma.backend.huertos.service; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import net.miarma.backlib.exception.BadRequestException; +import net.miarma.backlib.exception.ConflictException; +import net.miarma.backlib.exception.NotFoundException; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.model.UserMetadata; +import net.miarma.backend.huertos.repository.UserMetadataRepository; +import net.miarma.backlib.util.UuidUtil; + +@Service +@Transactional +public class UserMetadataService { + + private final UserMetadataRepository repository; + + public UserMetadataService(UserMetadataRepository repository) { + this.repository = repository; + } + + public List getAll() { + return repository.findAll(); + } + + @Cacheable("metadataByUserId") + public UserMetadata getById(UUID userId) { + byte[] idBytes = UuidUtil.uuidToBin(userId); + return repository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Metadatos de usuario no encontrados")); + } + + @Cacheable("metadataByMemberNumber") + public UserMetadata getByMemberNumber(Integer memberNumber) { + return repository.findByMemberNumber(memberNumber) + .orElseThrow(() -> new NotFoundException("Metadatos de usuario no encontrados")); + } + + @Cacheable("metadataExists") + public boolean existsById(UUID userId) { + byte[] idBytes = UuidUtil.uuidToBin(userId); + return repository.existsById(idBytes); + } + + public UserMetadata create(UserMetadata meta) { + if (meta.getUserId() == null) { + throw new BadRequestException("El identificador de usuario es obligatorio"); + } + if (repository.existsById(UuidUtil.uuidToBin(meta.getUserId()))) { + throw new ConflictException("Este usuario ya tiene metadatos asociados"); + } + + if (meta.getMemberNumber() == null) throw new BadRequestException("El número de socio es obligatorio"); + if (meta.getPlotNumber() == null) throw new BadRequestException("El número de huerto es obligatorio"); + if (meta.getDni() == null || meta.getDni().isBlank()) throw new BadRequestException("El DNI es obligatorio"); + if (meta.getPhone() == null || meta.getPhone().isBlank()) throw new BadRequestException("El teléfono es obligatorio"); + if (meta.getType() == null) meta.setType((byte) 0); + if (meta.getRole() == null) meta.setRole((byte) 0); + + meta.setCreatedAt(Instant.now()); + meta.setAssignedAt(null); + meta.setDeactivatedAt(null); + + return repository.save(meta); + } + + @CacheEvict( + value = { + "metadataByUserId", + "metadataByMemberNumber", + "metadataExists" + }, + key = "#p0" + ) + public UserMetadata update(UUID userId, UserMetadata changes) { + byte[] idBytes = UuidUtil.uuidToBin(userId); + + UserMetadata metadata = repository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Metadatos de usuario no encontrados")); + + if (changes.getMemberNumber() != null) metadata.setMemberNumber(changes.getMemberNumber()); + if (changes.getPlotNumber() != null) metadata.setPlotNumber(changes.getPlotNumber()); + if (changes.getDni() != null) metadata.setDni(changes.getDni()); + if (changes.getPhone() != null) metadata.setPhone(changes.getPhone()); + if (changes.getType() != null) metadata.setType(changes.getType()); + if (changes.getRole() != null) metadata.setRole(changes.getRole()); + if (changes.getNotes() != null) metadata.setNotes(changes.getNotes()); + if (changes.getAssignedAt() != null) metadata.setAssignedAt(changes.getAssignedAt()); + if (changes.getDeactivatedAt() != null) metadata.setDeactivatedAt(changes.getDeactivatedAt()); + + return repository.save(metadata); + } + + public void delete(UUID userId) { + byte[] idBytes = UuidUtil.uuidToBin(userId); + if (!repository.existsById(idBytes)) { + throw new NotFoundException("Metadatos de usuario no encontrados"); + } + repository.deleteById(idBytes); + } + + public Integer getLatestMemberNumber() { + return repository.findAll() + .stream() + .map(UserMetadata::getMemberNumber) + .max(Integer::compareTo) + .get(); + } + + public Boolean existsByMemberNumber(Integer memberNumber) { + return getByMemberNumber(memberNumber).getUserId() != null; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/view/VBalanceWithTotalsService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/view/VBalanceWithTotalsService.java new file mode 100644 index 0000000..68aabd3 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/view/VBalanceWithTotalsService.java @@ -0,0 +1,24 @@ +package net.miarma.backend.huertos.service.view; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.model.view.VBalanceWithTotals; +import net.miarma.backend.huertos.repository.view.VBalanceWithTotalsRepository; + +@Service +@Transactional +public class VBalanceWithTotalsService { + + private final VBalanceWithTotalsRepository repository; + + public VBalanceWithTotalsService(VBalanceWithTotalsRepository repository) { + this.repository = repository; + } + + public List getAll() { + return repository.findAll(); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/service/view/VIncomesWithInfoService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/view/VIncomesWithInfoService.java new file mode 100644 index 0000000..2bd7349 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/view/VIncomesWithInfoService.java @@ -0,0 +1,44 @@ +package net.miarma.backend.huertos.service.view; + +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +import net.miarma.backend.huertos.model.Income; +import net.miarma.backlib.exception.NotFoundException; +import org.springframework.stereotype.Service; + +import jakarta.transaction.Transactional; +import net.miarma.backend.huertos.model.view.VIncomesWithInfo; +import net.miarma.backend.huertos.repository.view.VIncomesWithInfoRepository; +import net.miarma.backlib.util.UuidUtil; + +@Service +@Transactional +public class VIncomesWithInfoService { + + private final VIncomesWithInfoRepository repository; + + public VIncomesWithInfoService(VIncomesWithInfoRepository repository) { + this.repository = repository; + } + + public List getAll() { + return repository.findAll().stream() + .sorted(Comparator.comparing(VIncomesWithInfo::getCreatedAt).reversed()) + .toList(); + } + + public VIncomesWithInfo getById(UUID incomeId) { + byte[] idBytes = UuidUtil.uuidToBin(incomeId); + return repository.findById(idBytes) + .orElseThrow(() -> new NotFoundException("Ingreso no encontrado")); + } + + public List getByUserId(UUID userId) { + byte[] idBytes = UuidUtil.uuidToBin(userId); + return repository.findAll().stream() + .filter(i -> i.getUserId().equals(userId)) + .toList(); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/util/UsernameGenerator.java b/huertos/src/main/java/net/miarma/backend/huertos/util/UsernameGenerator.java new file mode 100644 index 0000000..1f7018b --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/util/UsernameGenerator.java @@ -0,0 +1,9 @@ +package net.miarma.backend.huertos.util; + +import java.util.Locale; + +public class UsernameGenerator { + public static String generate(String name, Integer number) { + return name.split(" ")[0].toLowerCase() + number; + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/validation/DniValidator.java b/huertos/src/main/java/net/miarma/backend/huertos/validation/DniValidator.java new file mode 100644 index 0000000..10af1f6 --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/validation/DniValidator.java @@ -0,0 +1,66 @@ +package net.miarma.backend.huertos.validation; + +/** + * Validador de DNI/NIE español. + *

+ * Este validador comprueba si un DNI o NIE es válido según las reglas establecidas por la legislación española. + * Un DNI debe tener 8 dígitos seguidos de una letra, y un NIE debe comenzar con X, Y o Z seguido de 7 dígitos y una letra. + * + * @author José Manuel Amador Gallardo + */ +public class DniValidator { + + /** + * Valida un DNI o NIE español. + * + * @param id El DNI o NIE a validar. + * @return true si el DNI/NIE es válido, false en caso contrario. + */ + public static boolean isValid(String id) { + if (id == null || id.length() != 9) { + return false; + } + + id = id.toUpperCase(); // Pa evitar problemas con minúsculas + String numberPart; + char letterPart = id.charAt(8); + + if (id.startsWith("X") || id.startsWith("Y") || id.startsWith("Z")) { + // NIE + char prefix = id.charAt(0); + String numericPrefix = switch (prefix) { + case 'X' -> "0"; + case 'Y' -> "1"; + case 'Z' -> "2"; + default -> null; + }; + + if (numericPrefix == null) return false; + + numberPart = numericPrefix + id.substring(1, 8); + } else { + // DNI + numberPart = id.substring(0, 8); + } + + if (!numberPart.matches("\\d{8}")) { + return false; + } + + int number = Integer.parseInt(numberPart); + char expectedLetter = calculateLetter(number); + + return letterPart == expectedLetter; + } + + /** + * Calcula la letra correspondiente a un número de DNI. + * + * @param number El número del DNI (8 dígitos). + * @return La letra correspondiente. + */ + private static char calculateLetter(int number) { + String letters = "TRWAGMYFPDXBNJZSQVHLCKE"; + return letters.charAt(number % 23); + } +} diff --git a/huertos/src/main/java/net/miarma/backend/huertos/validation/RequestValidator.java b/huertos/src/main/java/net/miarma/backend/huertos/validation/RequestValidator.java new file mode 100644 index 0000000..66b4e1d --- /dev/null +++ b/huertos/src/main/java/net/miarma/backend/huertos/validation/RequestValidator.java @@ -0,0 +1,90 @@ +package net.miarma.backend.huertos.validation; + +import net.miarma.backend.huertos.model.RequestMetadata; +import net.miarma.backlib.exception.BadRequestException; +import net.miarma.backlib.exception.ValidationException; + +import java.util.regex.Pattern; + +public class RequestValidator { + + private static final Pattern DNI_PATTERN = Pattern.compile("\\d{8}[A-Za-z]"); + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\w.%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$"); + private static final Pattern PHONE_PATTERN = Pattern.compile("^\\+?\\d{9,15}$"); + + public static void validate(RequestMetadata metadata, Byte requestType) { + if (metadata.getRequestId() == null) { + throw new BadRequestException("Estos metadatos deben pertenecer a una solicitud (falta ID)"); + } + + if (isBlank(metadata.getDisplayName())) { + throw new BadRequestException("El nombre a mostrar es obligatorio"); + } else if (metadata.getDisplayName().length() < 3) { + throw new ValidationException("displayName", "El nombre a mostrar debe tener al menos 3 caracteres"); + } + + if (isBlank(metadata.getDni())) { + throw new BadRequestException("El DNI es obligatorio"); + } else if (!DNI_PATTERN.matcher(metadata.getDni()).matches()) { + throw new ValidationException("dni", "Formato de DNI inválido (ej: 12345678A)"); + } else if (!DniValidator.isValid(metadata.getDni())) { + throw new ValidationException("dni", "Este DNI no es un DNI real"); + } + + if (isBlank(metadata.getEmail())) { + throw new BadRequestException("El email es obligatorio"); + } else if (!EMAIL_PATTERN.matcher(metadata.getEmail()).matches()) { + throw new ValidationException("email", "Email inválido"); + } + + if (isBlank(metadata.getUsername())) { + throw new BadRequestException("El usuario es obligatorio"); + } else if (metadata.getUsername().length() < 3) { + throw new ValidationException("username", "El usuario debe tener al menos 3 caracteres"); + } + + if (metadata.getType() == null) { + throw new BadRequestException("El tipo de usuario es obligatorio"); + } + + if (requestType == 2) { + if (metadata.getPlotNumber() == null) { + throw new BadRequestException("El colaborador debe tener huerto"); + } + } + + if (requestType == 0 || requestType == 1) { + if (metadata.getMemberNumber() == null) { + throw new BadRequestException("El número de socio es obligatorio"); + } + } + + if (requestType == 0) { + if (metadata.getAddress() == null || metadata.getZipCode() == null || metadata.getCity() == null) { + throw new BadRequestException("La dirección, código postal y ciudad son obligatorios"); + } + } + + if (requestType == 0) { + if (isBlank(metadata.getAddress())) { + throw new ValidationException("address", "La dirección es obligatoria"); + } + if (isBlank(metadata.getZipCode())) { + throw new ValidationException("zipCode", "El código postal es obligatorio"); + } else if(metadata.getZipCode().length() < 5) { + throw new ValidationException("zipCode", "El código postal debe tener 5 dígitos"); + } + if (isBlank(metadata.getCity())) { + throw new ValidationException("city", "La ciudad es obligatoria"); + } + } + + if (metadata.getPhone() != null && !PHONE_PATTERN.matcher(metadata.getPhone()).matches()) { + throw new ValidationException("phone", "Teléfono inválido (debe tener 9 dígitos)"); + } + } + + private static boolean isBlank(String s) { + return s == null || s.trim().isEmpty(); + } +} diff --git a/huertos/src/main/resources/application-dev.yml b/huertos/src/main/resources/application-dev.yml new file mode 100644 index 0000000..c0e66ab --- /dev/null +++ b/huertos/src/main/resources/application-dev.yml @@ -0,0 +1,28 @@ +server: + port: 8081 + servlet: + context-path: /v2/huertos + +spring: + datasource: + url: jdbc:mariadb://localhost:3306/miarma_v2 + username: admin + password: ${DB_PASS} + driver-class-name: org.mariadb.jdbc.Driver + +logging: + level: + org.hibernate.SQL: DEBUG + org.hibernate.orm.jdbc.bind: TRACE + +core: + url: http://localhost:8080/v2/core + +huertos: + user: SYSTEM + password: ${HUERTOS_PASS} + +mail: + smtp: + server: smtp.dondominio.com + port: 587 \ No newline at end of file diff --git a/huertos/src/main/resources/application-prod.yml b/huertos/src/main/resources/application-prod.yml new file mode 100644 index 0000000..56dca05 --- /dev/null +++ b/huertos/src/main/resources/application-prod.yml @@ -0,0 +1,27 @@ +server: + port: 8081 + servlet: + context-path: /v2/huertos + +spring: + datasource: + url: jdbc:mariadb://mariadb:3306/miarma_v2 + username: ${DB_USER} + password: ${DB_PASS} + driver-class-name: org.mariadb.jdbc.Driver + +logging: + level: + org.hibernate.SQL: WARN + +core: + url: http://core:8080/v2/core + +huertos: + user: SYSTEM + password: ${HUERTOS_PASS} + +mail: + smtp: + server: smtp.dondominio.com + port: 587 \ No newline at end of file diff --git a/huertos/src/main/resources/application.yml b/huertos/src/main/resources/application.yml new file mode 100644 index 0000000..87de0a8 --- /dev/null +++ b/huertos/src/main/resources/application.yml @@ -0,0 +1,25 @@ +spring: + application: + name: huertos-service + + jpa: + open-in-view: false + hibernate: + ddl-auto: validate + properties: + hibernate: + jdbc: + time_zone: UTC + + jackson: + default-property-inclusion: non_null + time-zone: Europe/Madrid + +jwt: + expiration-ms: 3600000 + +management: + endpoints: + web: + exposure: + include: health,info diff --git a/minecraft/.classpath b/minecraft/.classpath deleted file mode 100644 index f7e4a1d..0000000 --- a/minecraft/.classpath +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/minecraft/.project b/minecraft/.project deleted file mode 100644 index b356ec0..0000000 --- a/minecraft/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - minecraft - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/minecraft/.settings/org.eclipse.jdt.core.prefs b/minecraft/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 2f5cc74..0000000 --- a/minecraft/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/minecraft/.settings/org.eclipse.m2e.core.prefs b/minecraft/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/minecraft/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/minecraft/minecraft.iml b/minecraft/minecraft.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/minecraft/minecraft.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/minecraft/pom.xml b/minecraft/pom.xml index a72edf0..2531777 100644 --- a/minecraft/pom.xml +++ b/minecraft/pom.xml @@ -9,6 +9,18 @@ minecraft + + 25 + 25 + + + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + diff --git a/mpaste/.classpath b/mpaste/.classpath deleted file mode 100644 index f7e4a1d..0000000 --- a/mpaste/.classpath +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mpaste/.project b/mpaste/.project deleted file mode 100644 index 499668e..0000000 --- a/mpaste/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - mpaste - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/mpaste/.settings/org.eclipse.jdt.core.prefs b/mpaste/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 2f5cc74..0000000 --- a/mpaste/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,8 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/mpaste/.settings/org.eclipse.m2e.core.prefs b/mpaste/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/mpaste/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/mpaste/mpaste.iml b/mpaste/mpaste.iml new file mode 100644 index 0000000..68a9707 --- /dev/null +++ b/mpaste/mpaste.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/mpaste/pom.xml b/mpaste/pom.xml index 4a8b476..fbff362 100644 --- a/mpaste/pom.xml +++ b/mpaste/pom.xml @@ -9,6 +9,18 @@ mpaste + + 25 + 25 + + + + + gitea + https://git.miarma.net/api/packages/Gallardo7761/maven + + + diff --git a/pom.xml b/pom.xml index 4e76ab2..5acf6c0 100644 --- a/pom.xml +++ b/pom.xml @@ -15,12 +15,14 @@ minecraft cine mpaste - websocket + backlib 25 4.0.1 + 25 + 25 diff --git a/target/classes/META-INF/MANIFEST.MF b/target/classes/META-INF/MANIFEST.MF deleted file mode 100644 index a5622f8..0000000 --- a/target/classes/META-INF/MANIFEST.MF +++ /dev/null @@ -1,4 +0,0 @@ -Manifest-Version: 1.0 -Build-Jdk-Spec: 21 -Created-By: Maven Integration for Eclipse - diff --git a/target/classes/META-INF/maven/net.miarma/backend/pom.properties b/target/classes/META-INF/maven/net.miarma/backend/pom.properties deleted file mode 100644 index e27ed27..0000000 --- a/target/classes/META-INF/maven/net.miarma/backend/pom.properties +++ /dev/null @@ -1,7 +0,0 @@ -#Generated by Maven Integration for Eclipse -#Fri Jan 16 02:26:12 CET 2026 -artifactId=backend -groupId=net.miarma -m2e.projectLocation=/home/jomaa/git/miarma-backend -m2e.projectName=backend -version=1.0.0 diff --git a/target/classes/META-INF/maven/net.miarma/backend/pom.xml b/target/classes/META-INF/maven/net.miarma/backend/pom.xml deleted file mode 100644 index 3b1fee6..0000000 --- a/target/classes/META-INF/maven/net.miarma/backend/pom.xml +++ /dev/null @@ -1,6 +0,0 @@ - - 4.0.0 - net.miarma - backend - 1.0.0 - \ No newline at end of file