Add: CORS handling for dev profile. Fix: custom dates on socios, ingresos and gastos. Add: new endpoint for socios dropdown on ingresos

This commit is contained in:
Jose
2026-02-01 14:43:39 +01:00
parent d3c19a6186
commit 6a5a4b6871
18 changed files with 188 additions and 17 deletions

View File

@@ -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> 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))

View File

@@ -31,7 +31,7 @@ public class MemberController {
@GetMapping
@PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')")
public ResponseEntity<List<MemberDto>> 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<List<DropdownDto>> getDropdown() {
return ResponseEntity.ok(memberService.getDropdown());
}
@GetMapping("/{user_id:[0-9a-fA-F\\-]{36}}")
@PreAuthorize("hasAnyRole('HUERTOS_ROLE_ADMIN', 'HUERTOS_ROLE_DEV')")
public ResponseEntity<MemberDto> getById(@PathVariable("user_id") UUID userId) {

View File

@@ -0,0 +1,6 @@
package net.miarma.backend.huertos.dto;
import java.util.UUID;
public record DropdownDto(UUID userId, Integer memberNumber, String displayName) {
}

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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<MemberDto> getAll(Byte serviceId) {
List<UserWithCredentialDto> all = huertosWebClient.getAllUsersWithCredentials(serviceId);
public List<MemberDto> getAll() {
List<UserWithCredentialDto> 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<MemberDto> all = getAll((byte)1);
List<MemberDto> 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<DropdownDto> getDropdown() {
return getAll().stream()
.map(DropdownDtoMapper::toDto)
.toList();
}
}

View File

@@ -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)");
}
}