diff --git a/TODO b/TODO index 755922e..f46a95e 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ POR HACER -------------------------------- +- implementar urlParams para filtros - documentación - mail wrapper @@ -8,7 +9,9 @@ RESUELTO --------------------------------- - 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 -- implementar urlParams para filtros -> NO RESUELTO (DEPRECATED) - 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 \ No newline at end of file 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/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backlib/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 9b45e3d..30ddccf 100644 --- 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 @@ -21,6 +21,8 @@ /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 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/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java b/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java index 74b7264..8e345fb 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 @@ -12,6 +12,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe 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 @@ -20,19 +23,26 @@ public class SecurityConfig { private final JwtFilter jwtFilter; private final RestAuthEntryPoint authEntryPoint; private final RestAccessDeniedHandler accessDeniedHandler; + private final CorsConfigurationSource corsConfigurationSource; public SecurityConfig( JwtFilter jwtFilter, RestAuthEntryPoint authEntryPoint, - RestAccessDeniedHandler accessDeniedHandler + 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)) 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 index 9cabcfc..2b3f704 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/config/SecurityConfig.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/config/SecurityConfig.java @@ -5,12 +5,16 @@ 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 @@ -20,19 +24,26 @@ 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 + 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)) 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 index ad1887f..92f65b0 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/controller/MemberController.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/controller/MemberController.java @@ -31,7 +31,7 @@ public class MemberController { @GetMapping @PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')") public ResponseEntity> getAll() { - return ResponseEntity.ok(memberService.getAll((byte)1)); + return ResponseEntity.ok(memberService.getAll()); } @GetMapping("/me") @@ -45,6 +45,12 @@ public class MemberController { ); } + @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) { 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 index 541b27a..42299b5 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/dto/ExpenseDto.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/ExpenseDto.java @@ -11,6 +11,7 @@ public class ExpenseDto { private String supplier; private String invoice; private Byte type; + private Instant createdAt; public String getConcept() { return concept; @@ -51,6 +52,14 @@ public class ExpenseDto { 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 { @@ -59,6 +68,8 @@ public class ExpenseDto { private BigDecimal amount; private String supplier; private String invoice; + private Byte type; + private Instant createdAt; public UUID getExpenseId() { return expenseId; @@ -115,8 +126,5 @@ public class ExpenseDto { public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } - - private Byte type; - private Instant createdAt; } } 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 index fe6a7e5..274ec64 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/dto/IncomeDto.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/dto/IncomeDto.java @@ -12,6 +12,7 @@ public class IncomeDto { private BigDecimal amount; private Byte type; private Byte frequency; + private Instant createdAt; public Integer getMemberNumber() { return memberNumber; @@ -60,6 +61,14 @@ public class IncomeDto { 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 { 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 index f750d5a..701258c 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/mapper/ExpenseMapper.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/ExpenseMapper.java @@ -28,6 +28,7 @@ public class ExpenseMapper { 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 index 6a52b41..ea40403 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/mapper/IncomeMapper.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/mapper/IncomeMapper.java @@ -28,6 +28,7 @@ public class IncomeMapper { 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/service/ExpenseService.java b/huertos/src/main/java/net/miarma/backend/huertos/service/ExpenseService.java index 92c9eb6..70d3375 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/service/ExpenseService.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/ExpenseService.java @@ -46,9 +46,11 @@ public class ExpenseService { 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()); - expense.setCreatedAt(Instant.now()); return expenseRepository.save(expense); } 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 index fdb2efd..a7192dd 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/service/IncomeService.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/IncomeService.java @@ -1,6 +1,7 @@ package net.miarma.backend.huertos.service; import java.time.Instant; +import java.time.temporal.TemporalAmount; import java.util.List; import java.util.UUID; @@ -56,9 +57,11 @@ public class IncomeService { 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()); - income.setCreatedAt(Instant.now()); return incomeRepository.save(income); } 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 index c1f4f4a..1bc22e2 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/service/MemberService.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/service/MemberService.java @@ -2,6 +2,7 @@ import net.miarma.backend.huertos.client.HuertosWebClient; import net.miarma.backend.huertos.dto.*; + 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; @@ -52,8 +53,8 @@ } @Cacheable("members") - public List getAll(Byte serviceId) { - List all = huertosWebClient.getAllUsersWithCredentials(serviceId); + public List getAll() { + List all = huertosWebClient.getAllUsersWithCredentials((byte)1); return all.stream() .filter(uwc -> metadataService.existsById(uwc.user().getUserId())) @@ -125,21 +126,21 @@ } public MemberDto getByMemberNumber(Integer memberNumber) { - return getAll((byte)1).stream() + 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((byte)1).stream() + 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((byte)1).stream() + return getAll().stream() .filter(dto -> dni.equals(dto.metadata().getDni())) .findFirst() .orElseThrow(() -> new NotFoundException("No hay socio con ese DNI")); @@ -156,7 +157,7 @@ } public Boolean hasCollaborator(Integer memberNumber) { - List all = getAll((byte)1); + List all = getAll(); var member = all.stream() .filter(dto -> dto.metadata().getMemberNumber().equals(memberNumber)) @@ -189,4 +190,10 @@ 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/validation/RequestValidator.java b/huertos/src/main/java/net/miarma/backend/huertos/validation/RequestValidator.java index 3e75fb9..66b4e1d 100644 --- a/huertos/src/main/java/net/miarma/backend/huertos/validation/RequestValidator.java +++ b/huertos/src/main/java/net/miarma/backend/huertos/validation/RequestValidator.java @@ -49,7 +49,7 @@ public class RequestValidator { if (requestType == 2) { if (metadata.getPlotNumber() == null) { - throw new BadRequestException("El colaborador debe tener parcela"); + throw new BadRequestException("El colaborador debe tener huerto"); } } @@ -65,8 +65,22 @@ public class RequestValidator { } } + 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 entre 9 y 15 dígitos, opcional +)"); + throw new ValidationException("phone", "Teléfono inválido (debe tener 9 dígitos)"); } }