diff --git a/pom.xml b/pom.xml index 3b28882..bc1d869 100644 --- a/pom.xml +++ b/pom.xml @@ -13,8 +13,8 @@ com.members msvc-members 0.0.1-SNAPSHOT - msvc-members - msvc-members + msvc-classes + msvc-classes @@ -122,11 +122,6 @@ 1.5.5.Final provided - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.8.11 - com.cloudinary cloudinary-http44 @@ -176,7 +171,6 @@ - diff --git a/src/main/java/com/classes/config/SecurityConfig.java b/src/main/java/com/classes/config/SecurityConfig.java index 38694cc..26e0134 100644 --- a/src/main/java/com/classes/config/SecurityConfig.java +++ b/src/main/java/com/classes/config/SecurityConfig.java @@ -30,7 +30,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/swagger-ui/**", "/v3/api-docs/**", "/saludo", - "/" + "/", + "/test/**" ).permitAll() .anyRequest().authenticated() ) diff --git a/src/main/java/com/classes/controllers/ClassController.java b/src/main/java/com/classes/controllers/ClassController.java new file mode 100644 index 0000000..c7188d6 --- /dev/null +++ b/src/main/java/com/classes/controllers/ClassController.java @@ -0,0 +1,53 @@ +package com.classes.controllers; + +import com.classes.dtos.Class.ClassRequest; +import com.classes.dtos.Class.ClassResponse; +import com.classes.services.ClassService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/classes") +@RequiredArgsConstructor +public class ClassController { + + private final ClassService classService; + + + + @PostMapping + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)") + public ResponseEntity createClass(@RequestBody ClassRequest request) { + ClassResponse created = classService.createClass(request); + return ResponseEntity.status(HttpStatus.CREATED).body(created); + } + + @GetMapping + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)") + public ResponseEntity> findAllClasses() { + List list = classService.findAll(); + return ResponseEntity.ok(list); + } + + + @PutMapping("/{id}") + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)") + public ResponseEntity updateClass(@PathVariable UUID id, @RequestBody ClassRequest request) { + ClassResponse updated = classService.updateClass(id, request); + return ResponseEntity.ok(updated); + } + + @DeleteMapping("/{id}") + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)") + public ResponseEntity deleteClass(@PathVariable UUID id) { + classService.deleteClass(id); + return ResponseEntity.ok("Clase eliminada correctamente"); + } +} diff --git a/src/main/java/com/classes/controllers/HelloController.java b/src/main/java/com/classes/controllers/HelloController.java index 05b7594..365769b 100644 --- a/src/main/java/com/classes/controllers/HelloController.java +++ b/src/main/java/com/classes/controllers/HelloController.java @@ -1,12 +1,15 @@ package com.classes.controllers; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/saludo") +@RequestMapping("/test") public class HelloController { - public String saludo() { - return "Microservicio de clases activo"; + @GetMapping("saludo") + public ResponseEntity saludo() { + return ResponseEntity.ok("Microservicio de clases activo"); } } diff --git a/src/main/java/com/classes/controllers/LocationController.java b/src/main/java/com/classes/controllers/LocationController.java index 7d4a738..8efd485 100644 --- a/src/main/java/com/classes/controllers/LocationController.java +++ b/src/main/java/com/classes/controllers/LocationController.java @@ -1,16 +1,16 @@ package com.classes.controllers; import com.classes.annotations.AdminOrTrainerAccess; -import com.classes.dtos.LocationDTO; -import com.classes.entities.LocationEntity; +import com.classes.dtos.Location.LocationRequest; +import com.classes.dtos.Location.LocationResponse; import com.classes.services.LocationService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; 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.UUID; @RestController @@ -21,29 +21,45 @@ public class LocationController { private final LocationService locationService; + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @PostMapping - public ResponseEntity create(@RequestBody LocationDTO dto) { - LocationDTO created = locationService.create(dto); + public ResponseEntity create(@RequestBody LocationRequest request) { + LocationResponse created = locationService.create(request); return new ResponseEntity<>(created, HttpStatus.CREATED); } + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @GetMapping - public ResponseEntity> findAll( + public ResponseEntity> findAll( @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String search, + @RequestParam(required = false) Boolean active ) { - Page locations = locationService.findAll(page, size); + Page locations = locationService.findAll(page, size, search, active); return ResponseEntity.ok(locations); } - + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @GetMapping("/{id}") - public ResponseEntity findById(@PathVariable UUID id) { - return ResponseEntity.ok(locationService.findById(id)); + public ResponseEntity findById(@PathVariable UUID id) { + LocationResponse location = locationService.findById(id); + return ResponseEntity.ok(location); + } + + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") + @PutMapping("/{id}") + public ResponseEntity update( + @PathVariable UUID id, + @RequestBody LocationRequest request + ) { + LocationResponse updated = locationService.update(id, request); + return ResponseEntity.ok(updated); } + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @DeleteMapping("/{id}") public ResponseEntity delete(@PathVariable UUID id) { - locationService.delete(id); + locationService.delete(id); // Validación interna return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/com/classes/controllers/TrainerController.java b/src/main/java/com/classes/controllers/TrainerController.java index 40ed7c7..07db9e2 100644 --- a/src/main/java/com/classes/controllers/TrainerController.java +++ b/src/main/java/com/classes/controllers/TrainerController.java @@ -1,15 +1,19 @@ package com.classes.controllers; -import com.classes.dtos.TrainerDTO; +import com.classes.dtos.Trainer.TrainerDTO; +import com.classes.services.AuthorizationService; import com.classes.services.TrainerService; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -22,12 +26,17 @@ @RestController @RequestMapping("/api/trainers") @RequiredArgsConstructor +@Slf4j public class TrainerController { private final TrainerService trainerService; + private final AuthorizationService authService; +/*probado*/ + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createTrainer( + Authentication authentication, @RequestParam("trainer") String trainerJson, @RequestParam(value = "profileImage", required = false) MultipartFile profileImage, @RequestParam(value = "certifications", required = false) List certifications @@ -39,19 +48,23 @@ public ResponseEntity createTrainer( TrainerDTO createdTrainer = trainerService.createTrainer(trainerDTO, profileImage, certifications); return ResponseEntity.status(HttpStatus.CREATED).body(createdTrainer); } - + /*probado*/ + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @GetMapping("/{id}") public ResponseEntity getTrainerById(@PathVariable UUID id) { TrainerDTO trainer = trainerService.getTrainerById(id); return ResponseEntity.ok(trainer); } + //probado + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @GetMapping public ResponseEntity> getAllTrainers() { List trainers = trainerService.getAllTrainers(); return ResponseEntity.ok(trainers); } + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id, authentication)") @PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateTrainer( @PathVariable UUID id, @@ -67,16 +80,21 @@ public ResponseEntity updateTrainer( return ResponseEntity.ok(updatedTrainer); } + + @PreAuthorize("@authorizationServiceImpl.canAccessResource(#id,authentication)") @DeleteMapping("/{id}") public ResponseEntity deleteTrainer(@PathVariable UUID id) { try { trainerService.deleteTrainer(id); return ResponseEntity.noContent().build(); - } catch (EntityNotFoundException e) { + } catch ( + EntityNotFoundException e) { return ResponseEntity.notFound().build(); - } catch (IOException e) { + } catch ( + IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } } + diff --git a/src/main/java/com/classes/dtos/Class/ClassRequest.java b/src/main/java/com/classes/dtos/Class/ClassRequest.java new file mode 100644 index 0000000..b7653c6 --- /dev/null +++ b/src/main/java/com/classes/dtos/Class/ClassRequest.java @@ -0,0 +1,27 @@ +package com.classes.dtos.Class; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ClassRequest { + private String className; + private UUID locationId; + private UUID trainerId; + @JsonFormat(pattern = "dd-MM-yyyy") + private LocalDate classDate; + private int duration; + private int maxCapacity; + private LocalTime startTime; + private LocalTime endTime; + private boolean active; + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/classes/dtos/Class/ClassResponse.java b/src/main/java/com/classes/dtos/Class/ClassResponse.java new file mode 100644 index 0000000..baf76a4 --- /dev/null +++ b/src/main/java/com/classes/dtos/Class/ClassResponse.java @@ -0,0 +1,28 @@ +package com.classes.dtos.Class; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ClassResponse { + private UUID id; + private String className; + private String locationName; + private String trainerName; + @JsonFormat(pattern = "dd-MM-yyyy") + private LocalDate classDate; + private int duration; + private int maxCapacity; + private String schedule; + private boolean active; + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/classes/dtos/LocationDTO.java b/src/main/java/com/classes/dtos/Location/LocationRequest.java similarity index 67% rename from src/main/java/com/classes/dtos/LocationDTO.java rename to src/main/java/com/classes/dtos/Location/LocationRequest.java index f3a1d08..f065e52 100644 --- a/src/main/java/com/classes/dtos/LocationDTO.java +++ b/src/main/java/com/classes/dtos/Location/LocationRequest.java @@ -1,10 +1,9 @@ -package com.classes.dtos; - +package com.classes.dtos.Location; import lombok.Data; @Data -public class LocationDTO { +public class LocationRequest { private String name; private String description; private int ability; diff --git a/src/main/java/com/classes/dtos/Location/LocationResponse.java b/src/main/java/com/classes/dtos/Location/LocationResponse.java new file mode 100644 index 0000000..6ad0dba --- /dev/null +++ b/src/main/java/com/classes/dtos/Location/LocationResponse.java @@ -0,0 +1,14 @@ +package com.classes.dtos.Location; + +import lombok.Data; + +import java.util.UUID; + +@Data +public class LocationResponse { + private UUID id; // si tienes un ID generado + private String name; + private String description; + private int ability; + private boolean active; +} \ No newline at end of file diff --git a/src/main/java/com/classes/dtos/FileResponseDTO.java b/src/main/java/com/classes/dtos/Trainer/FileResponseDTO.java similarity index 90% rename from src/main/java/com/classes/dtos/FileResponseDTO.java rename to src/main/java/com/classes/dtos/Trainer/FileResponseDTO.java index c4c2e4b..ed64c33 100644 --- a/src/main/java/com/classes/dtos/FileResponseDTO.java +++ b/src/main/java/com/classes/dtos/Trainer/FileResponseDTO.java @@ -1,4 +1,4 @@ -package com.classes.dtos; +package com.classes.dtos.Trainer; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/classes/dtos/TrainerDTO.java b/src/main/java/com/classes/dtos/Trainer/TrainerDTO.java similarity index 75% rename from src/main/java/com/classes/dtos/TrainerDTO.java rename to src/main/java/com/classes/dtos/Trainer/TrainerDTO.java index f202d8f..ec47f27 100644 --- a/src/main/java/com/classes/dtos/TrainerDTO.java +++ b/src/main/java/com/classes/dtos/Trainer/TrainerDTO.java @@ -1,9 +1,9 @@ -package com.classes.dtos; +package com.classes.dtos.Trainer; -import com.classes.Enums.ContractType; -import com.classes.Enums.DayAvailability; -import com.classes.Enums.Gender; -import com.classes.Enums.TrainerStatus; +import com.classes.enums.ContractType; +import com.classes.enums.DayAvailability; +import com.classes.enums.Gender; +import com.classes.enums.TrainerStatus; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; @@ -18,7 +18,7 @@ public class TrainerDTO { private String lastName; private String dni; - @JsonFormat(pattern = "yyyy-MM-dd") + @JsonFormat(pattern = "dd-MM-yyyy") private LocalDate birthDate; private Gender gender; @@ -32,7 +32,7 @@ public class TrainerDTO { private List certifications; private Set availability; - @JsonFormat(pattern = "yyyy-MM-dd") + @JsonFormat(pattern = "dd-MM-yyyy") private LocalDate hireDate; private TrainerStatus status; diff --git a/src/main/java/com/classes/entities/ClassEntity.java b/src/main/java/com/classes/entities/ClassEntity.java index 4ca9111..962b09e 100644 --- a/src/main/java/com/classes/entities/ClassEntity.java +++ b/src/main/java/com/classes/entities/ClassEntity.java @@ -5,6 +5,8 @@ import jakarta.persistence.*; import lombok.*; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.UUID; @Entity @@ -18,13 +20,25 @@ public class ClassEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - private String title; - @Column(length = 2000) + private String name; + + private String className; + private int duration; + + private int maxCapacity; + private LocalTime startTime; + private LocalTime endTime; + private boolean active; private String description; - @Column(name = "trainer_id") - private UUID trainerId; - @Column(name = "max_capacity") - private Integer maxCapacity; + + @ManyToOne + @JoinColumn(name = "location_id") + private LocationEntity location; + + @ManyToOne + @JoinColumn(name = "trainer_id") + private TrainerEntity trainer; + @Embedded private Audit audit; } diff --git a/src/main/java/com/classes/entities/TrainerEntity.java b/src/main/java/com/classes/entities/TrainerEntity.java index 61f9400..acbdb9b 100644 --- a/src/main/java/com/classes/entities/TrainerEntity.java +++ b/src/main/java/com/classes/entities/TrainerEntity.java @@ -1,9 +1,9 @@ package com.classes.entities; -import com.classes.Enums.ContractType; -import com.classes.Enums.DayAvailability; -import com.classes.Enums.Gender; -import com.classes.Enums.TrainerStatus; +import com.classes.enums.ContractType; +import com.classes.enums.DayAvailability; +import com.classes.enums.Gender; +import com.classes.enums.TrainerStatus; import com.classes.config.Audit; import com.classes.config.AuditListener; import jakarta.persistence.*; @@ -65,6 +65,7 @@ public class TrainerEntity { private BigDecimal salaryPerClass; private String bankInfo; private String notes; + @Embedded private Audit audit; diff --git a/src/main/java/com/classes/Enums/ContractType.java b/src/main/java/com/classes/enums/ContractType.java similarity index 78% rename from src/main/java/com/classes/Enums/ContractType.java rename to src/main/java/com/classes/enums/ContractType.java index c5d5b42..1b507b6 100644 --- a/src/main/java/com/classes/Enums/ContractType.java +++ b/src/main/java/com/classes/enums/ContractType.java @@ -1,4 +1,4 @@ -package com.classes.Enums; +package com.classes.enums; public enum ContractType { TIEMPO_COMPLETO, diff --git a/src/main/java/com/classes/Enums/DayAvailability.java b/src/main/java/com/classes/enums/DayAvailability.java similarity index 81% rename from src/main/java/com/classes/Enums/DayAvailability.java rename to src/main/java/com/classes/enums/DayAvailability.java index e394d67..ac2d5df 100644 --- a/src/main/java/com/classes/Enums/DayAvailability.java +++ b/src/main/java/com/classes/enums/DayAvailability.java @@ -1,4 +1,4 @@ -package com.classes.Enums; +package com.classes.enums; public enum DayAvailability { diff --git a/src/main/java/com/classes/Enums/Gender.java b/src/main/java/com/classes/enums/Gender.java similarity index 78% rename from src/main/java/com/classes/Enums/Gender.java rename to src/main/java/com/classes/enums/Gender.java index 543fa99..ba01901 100644 --- a/src/main/java/com/classes/Enums/Gender.java +++ b/src/main/java/com/classes/enums/Gender.java @@ -1,4 +1,4 @@ -package com.classes.Enums; +package com.classes.enums; public enum Gender { MASCULINO, // Masculino diff --git a/src/main/java/com/classes/Enums/TrainerStatus.java b/src/main/java/com/classes/enums/TrainerStatus.java similarity index 89% rename from src/main/java/com/classes/Enums/TrainerStatus.java rename to src/main/java/com/classes/enums/TrainerStatus.java index 68fb885..ff5af03 100644 --- a/src/main/java/com/classes/Enums/TrainerStatus.java +++ b/src/main/java/com/classes/enums/TrainerStatus.java @@ -1,4 +1,4 @@ -package com.classes.Enums; +package com.classes.enums; public enum TrainerStatus { ACTIVO, // Entrenador activo diff --git a/src/main/java/com/classes/exceptions/GlobalExceptionController.java b/src/main/java/com/classes/exceptions/GlobalExceptionController.java index 001c963..889de7f 100644 --- a/src/main/java/com/classes/exceptions/GlobalExceptionController.java +++ b/src/main/java/com/classes/exceptions/GlobalExceptionController.java @@ -1,5 +1,6 @@ package com.classes.exceptions; +import jakarta.persistence.EntityNotFoundException; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -7,6 +8,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; //import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -108,19 +110,28 @@ public ResponseEntity handleNoHandlerFoundException(NoHandlerFoun return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); } -// @ExceptionHandler(AccessDeniedException.class) -// public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { -// ErrorResponse errorResponse = new ErrorResponse( -// "ACCESS_DENIED", -// "Acceso denegado", -// Collections.singletonList("No tienes los permisos necesarios para realizar esta acción") -// ); -// -// log.warn("Access denied for user"); -// return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse); -// } + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { + ErrorResponse errorResponse = new ErrorResponse( + "ACCESS_DENIED", + "Acceso denegado", + Collections.singletonList("No tienes los permisos necesarios para realizar esta acción") + ); + log.warn("Access denied for user"); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse); + } + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity handleEntityNotFoundException(EntityNotFoundException ex) { + ErrorResponse errorResponse = new ErrorResponse( + "ENTITY_NOT_FOUND", + "No se encontró la entidad solicitada", + List.of(ex.getMessage()) + ); + log.warn("Entity not found: {}", ex.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } } \ No newline at end of file diff --git a/src/main/java/com/classes/mappers/ClassMapper.java b/src/main/java/com/classes/mappers/ClassMapper.java new file mode 100644 index 0000000..46985e2 --- /dev/null +++ b/src/main/java/com/classes/mappers/ClassMapper.java @@ -0,0 +1,34 @@ +package com.classes.mappers; + +import com.classes.config.MapStructConfig; +import com.classes.dtos.Class.ClassRequest; + +import com.classes.dtos.Class.ClassResponse; +import com.classes.entities.ClassEntity; + +import org.mapstruct.*; + +import java.util.List; + +@Mapper(config = MapStructConfig.class) +public interface ClassMapper { + + // Crear entidad desde el request + + @Mapping(target = "id", ignore = true) + @Mapping(target = "location", ignore = true) // se setean en el servicio + @Mapping(target = "trainer", ignore = true) + ClassEntity toEntity(ClassRequest request); + + @Mapping(target = "locationName", source = "location.name") + @Mapping(target = "trainerName", source = "trainer.firstName") + @Mapping(target = "schedule", expression = "java(entity.getStartTime() + \" - \" + entity.getEndTime())") + ClassResponse toResponse(ClassEntity entity); + + // Lista de respuestas + List toResponseList(List entities); + + // Actualizar entidad desde el request (solo los campos no nulos) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void updateFromRequest(ClassRequest request, @MappingTarget ClassEntity entity); +} diff --git a/src/main/java/com/classes/mappers/LocationMapper.java b/src/main/java/com/classes/mappers/LocationMapper.java index 4a6da5c..37cf259 100644 --- a/src/main/java/com/classes/mappers/LocationMapper.java +++ b/src/main/java/com/classes/mappers/LocationMapper.java @@ -1,7 +1,8 @@ package com.classes.mappers; -import com.classes.dtos.LocationDTO; +import com.classes.dtos.Location.LocationRequest; +import com.classes.dtos.Location.LocationResponse; import com.classes.config.MapStructConfig; import com.classes.entities.LocationEntity; import org.mapstruct.BeanMapping; @@ -12,11 +13,14 @@ @Mapper(config = MapStructConfig.class) public interface LocationMapper { - LocationDTO toDto(LocationEntity locationEntity); - LocationEntity toEntity(LocationDTO locationDTO); + LocationResponse toResponse(LocationEntity locationEntity); + // Request → Entity + LocationEntity toEntity(LocationRequest locationRequest); + + // Actualización parcial: Request → Entity @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) - void updateFromDto(LocationDTO dto, @MappingTarget LocationEntity entity); + void updateFromRequest(LocationRequest request, @MappingTarget LocationEntity entity); } diff --git a/src/main/java/com/classes/mappers/TrainerMapper.java b/src/main/java/com/classes/mappers/TrainerMapper.java index 1bf7d53..666f32f 100644 --- a/src/main/java/com/classes/mappers/TrainerMapper.java +++ b/src/main/java/com/classes/mappers/TrainerMapper.java @@ -1,7 +1,7 @@ package com.classes.mappers; import com.classes.config.MapStructConfig; -import com.classes.dtos.TrainerDTO; +import com.classes.dtos.Trainer.TrainerDTO; import com.classes.entities.TrainerEntity; import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; diff --git a/src/main/java/com/classes/repositories/ClassRepository.java b/src/main/java/com/classes/repositories/ClassRepository.java new file mode 100644 index 0000000..6e32432 --- /dev/null +++ b/src/main/java/com/classes/repositories/ClassRepository.java @@ -0,0 +1,15 @@ +package com.classes.repositories; + +import com.classes.entities.ClassEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface ClassRepository extends JpaRepository { + Optional findFirstByTrainerId(UUID trainerId); + Optional findFirstByLocationId(UUID locationId); + +} diff --git a/src/main/java/com/classes/repositories/LocationRepository.java b/src/main/java/com/classes/repositories/LocationRepository.java index aa10536..7bb3b09 100644 --- a/src/main/java/com/classes/repositories/LocationRepository.java +++ b/src/main/java/com/classes/repositories/LocationRepository.java @@ -3,11 +3,23 @@ import com.classes.entities.LocationEntity; -import java.util.UUID; - +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.UUID; + + @Repository public interface LocationRepository extends JpaRepository { + Page findByNameContainingIgnoreCase(String name, Pageable pageable); + + // 2️⃣ Solo activos + Page findByActiveTrue(Pageable pageable); + + // 3️⃣ Solo inactivos + Page findByActiveFalse(Pageable pageable); + Page findByNameContainingIgnoreCaseAndActive(String name, boolean active, Pageable pageable); + } diff --git a/src/main/java/com/classes/repositories/TrainerRepository.java b/src/main/java/com/classes/repositories/TrainerRepository.java index 5a0cdc2..9af8498 100644 --- a/src/main/java/com/classes/repositories/TrainerRepository.java +++ b/src/main/java/com/classes/repositories/TrainerRepository.java @@ -4,9 +4,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; @Repository public interface TrainerRepository extends JpaRepository { + + + } diff --git a/src/main/java/com/classes/services/AuthorizationService.java b/src/main/java/com/classes/services/AuthorizationService.java new file mode 100644 index 0000000..d62f206 --- /dev/null +++ b/src/main/java/com/classes/services/AuthorizationService.java @@ -0,0 +1,15 @@ +package com.classes.services; + +import org.springframework.security.core.Authentication; + +import java.util.UUID; + +public interface AuthorizationService { + boolean hasRole(Authentication authentication, String role); + + UUID getUserId(Authentication authentication); + + boolean canAccessResource(UUID ownerId, Authentication authentication); + + boolean canAccessWithRoleOrOwner(UUID ownerId, Authentication authentication, String role); +} diff --git a/src/main/java/com/classes/services/AzureService.java b/src/main/java/com/classes/services/AzureService.java index 3a3c6f3..0362893 100644 --- a/src/main/java/com/classes/services/AzureService.java +++ b/src/main/java/com/classes/services/AzureService.java @@ -1,6 +1,6 @@ package com.classes.services; -import com.classes.dtos.FileResponseDTO; +import com.classes.dtos.Trainer.FileResponseDTO; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; diff --git a/src/main/java/com/classes/services/ClassService.java b/src/main/java/com/classes/services/ClassService.java new file mode 100644 index 0000000..4d6f370 --- /dev/null +++ b/src/main/java/com/classes/services/ClassService.java @@ -0,0 +1,19 @@ +package com.classes.services; + +import com.classes.dtos.Class.ClassRequest; +import com.classes.dtos.Class.ClassResponse; + +import java.util.List; +import java.util.UUID; + +public interface ClassService { + + + ClassResponse createClass(ClassRequest request); + + List findAll(); + + ClassResponse updateClass(UUID id, ClassRequest request); + + void deleteClass(UUID id); +} diff --git a/src/main/java/com/classes/services/CloudinaryService.java b/src/main/java/com/classes/services/CloudinaryService.java index 8f12c0e..5bc238a 100644 --- a/src/main/java/com/classes/services/CloudinaryService.java +++ b/src/main/java/com/classes/services/CloudinaryService.java @@ -1,6 +1,6 @@ package com.classes.services; -import com.classes.dtos.FileResponseDTO; +import com.classes.dtos.Trainer.FileResponseDTO; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; diff --git a/src/main/java/com/classes/services/Impl/AuthorizationServiceImpl.java b/src/main/java/com/classes/services/Impl/AuthorizationServiceImpl.java new file mode 100644 index 0000000..00356fb --- /dev/null +++ b/src/main/java/com/classes/services/Impl/AuthorizationServiceImpl.java @@ -0,0 +1,37 @@ +package com.classes.services.Impl; + +import com.classes.services.AuthorizationService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +@Slf4j +public class AuthorizationServiceImpl implements AuthorizationService { + @Override + public boolean hasRole(Authentication authentication, String role) { + return authentication.getAuthorities().stream() + .anyMatch(a -> a.getAuthority().equals(role)); + } + + @Override + public UUID getUserId(Authentication authentication) { + String userIdStr = ((JwtAuthenticationToken) authentication).getToken().getClaim("user_id"); + return UUID.fromString(userIdStr); + } + + @Override + // Verifica si el usuario es admin o dueño del recurso + public boolean canAccessResource(UUID ownerId, Authentication authentication) { + return hasRole(authentication, "ROLE_ADMIN") || ownerId.equals(getUserId(authentication)); + } + + @Override + // Verifica si el usuario tiene un rol específico o es dueño del recurso + public boolean canAccessWithRoleOrOwner(UUID ownerId, Authentication authentication, String role) { + return hasRole(authentication, role) || ownerId.equals(getUserId(authentication)); + } +} diff --git a/src/main/java/com/classes/services/Impl/AzureServiceImpl.java b/src/main/java/com/classes/services/Impl/AzureServiceImpl.java index 9e3a8d6..7cf4514 100644 --- a/src/main/java/com/classes/services/Impl/AzureServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/AzureServiceImpl.java @@ -5,7 +5,7 @@ import com.azure.storage.blob.BlobServiceClient; import com.azure.storage.blob.BlobServiceClientBuilder; import com.azure.storage.common.StorageSharedKeyCredential; -import com.classes.dtos.FileResponseDTO; +import com.classes.dtos.Trainer.FileResponseDTO; import com.classes.services.AzureService; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/classes/services/Impl/ClassServiceImpl.java b/src/main/java/com/classes/services/Impl/ClassServiceImpl.java new file mode 100644 index 0000000..5274d7a --- /dev/null +++ b/src/main/java/com/classes/services/Impl/ClassServiceImpl.java @@ -0,0 +1,94 @@ +package com.classes.services.Impl; + +import com.classes.dtos.Class.ClassRequest; +import com.classes.dtos.Class.ClassResponse; +import com.classes.entities.ClassEntity; +import com.classes.entities.LocationEntity; +import com.classes.entities.TrainerEntity; +import com.classes.mappers.ClassMapper; +import com.classes.repositories.ClassRepository; +import com.classes.repositories.LocationRepository; +import com.classes.repositories.TrainerRepository; +import com.classes.services.ClassService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + + +@Service +@RequiredArgsConstructor +public class ClassServiceImpl implements ClassService { + + private final ClassRepository repository; + private final ClassMapper classMapper; + private final TrainerRepository trainerRepository; + private final LocationRepository locationRepository; + + + @Transactional + @Override + public ClassResponse createClass(ClassRequest request) { + validateTrainerAndLocation(request); + ClassEntity entity = classMapper.toEntity(request); + TrainerEntity trainer = trainerRepository.findById(request.getTrainerId()) + .orElseThrow(() -> new IllegalArgumentException("El trainer con ID " + request.getTrainerId() + " no existe")); + LocationEntity location = locationRepository.findById(request.getLocationId()) + .orElseThrow(() -> new IllegalArgumentException("La ubicación con ID " + request.getLocationId() + " no existe")); + entity.setTrainer(trainer); + entity.setLocation(location); + ClassEntity saved = repository.save(entity); + return classMapper.toResponse(saved); + } + + @Transactional + @Override + public List findAll() { + return classMapper.toResponseList(repository.findAll()); + } + + @Transactional + @Override + public ClassResponse updateClass(UUID id, ClassRequest request) { + + ClassEntity existing = repository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("La clase con ID " + id + " no existe")); + + + if (request.getTrainerId() != null) { + TrainerEntity trainer = trainerRepository.findById(request.getTrainerId()) + .orElseThrow(() -> new IllegalArgumentException("El trainer con ID " + request.getTrainerId() + " no existe")); + existing.setTrainer(trainer); + } + + + if (request.getLocationId() != null) { + LocationEntity location = locationRepository.findById(request.getLocationId()) + .orElseThrow(() -> new IllegalArgumentException("La ubicación con ID " + request.getLocationId() + " no existe")); + existing.setLocation(location); + } + classMapper.updateFromRequest(request, existing); + ClassEntity updated = repository.save(existing); + return classMapper.toResponse(updated); + } + + @Transactional + @Override + public void deleteClass(UUID id) { + if (!repository.existsById(id)) { + throw new IllegalArgumentException("La clase con ID " + id + " no existe"); + } + repository.deleteById(id); + } + + private void validateTrainerAndLocation(ClassRequest request) { + if (!trainerRepository.existsById(request.getTrainerId())) { + throw new IllegalArgumentException("El trainer con ID " + request.getTrainerId() + " no existe"); + } + if (!locationRepository.existsById(request.getLocationId())) { + throw new IllegalArgumentException("La ubicación con ID " + request.getLocationId() + " no existe"); + } + } +} diff --git a/src/main/java/com/classes/services/Impl/CloudinaryServiceImpl.java b/src/main/java/com/classes/services/Impl/CloudinaryServiceImpl.java index 550857c..c1f2c05 100644 --- a/src/main/java/com/classes/services/Impl/CloudinaryServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/CloudinaryServiceImpl.java @@ -1,6 +1,6 @@ package com.classes.services.Impl; -import com.classes.dtos.FileResponseDTO; +import com.classes.dtos.Trainer.FileResponseDTO; import com.classes.services.CloudinaryService; import com.cloudinary.Cloudinary; import com.cloudinary.utils.ObjectUtils; diff --git a/src/main/java/com/classes/services/Impl/LocationServiceImpl.java b/src/main/java/com/classes/services/Impl/LocationServiceImpl.java index 2a25aff..d7f8034 100644 --- a/src/main/java/com/classes/services/Impl/LocationServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/LocationServiceImpl.java @@ -1,20 +1,18 @@ package com.classes.services.Impl; -import com.classes.dtos.LocationDTO; +import com.classes.dtos.Location.LocationRequest; +import com.classes.dtos.Location.LocationResponse; import com.classes.entities.LocationEntity; import com.classes.mappers.LocationMapper; +import com.classes.repositories.ClassRepository; import com.classes.repositories.LocationRepository; import com.classes.services.LocationService; -import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; import java.util.UUID; @Service @@ -23,42 +21,62 @@ public class LocationServiceImpl implements LocationService { private final LocationRepository repository; private final LocationMapper mapper; + private final ClassRepository classRepository; @Override @Transactional - public LocationDTO create(LocationDTO dto) { - LocationEntity locationEntity = mapper.toEntity(dto); - return mapper.toDto(repository.save(locationEntity)); + public LocationResponse create(LocationRequest request) { + LocationEntity entity = mapper.toEntity(request); + LocationEntity saved = repository.save(entity); + return mapper.toResponse(saved); } @Override @Transactional - public LocationDTO update(UUID id, LocationDTO dto) { - LocationEntity locationEntity = findById(id); - mapper.updateFromDto(dto, locationEntity); - LocationEntity updated = repository.save(locationEntity); - return mapper.toDto(updated); - } - - - @Override - @Transactional(readOnly = true) - public LocationEntity findById(UUID id) { - return repository.findById(id) + public LocationResponse update(UUID id, LocationRequest request) { + LocationEntity entity = repository.findById(id) .orElseThrow(() -> new RuntimeException("Location not found")); + mapper.updateFromRequest(request, entity); + LocationEntity updated = repository.save(entity); + return mapper.toResponse(updated); } - @Transactional @Override + @Transactional public void delete(UUID id) { + boolean hasClasses = classRepository.findFirstByLocationId(id).isPresent(); + if (hasClasses) { + throw new IllegalArgumentException( + "No se puede eliminar porque hay clases asociadas con este ID" + ); + } repository.deleteById(id); } - @Transactional(readOnly = true) - public Page findAll(int page, int size) { - Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending()); - Page pageEntity = repository.findAll(pageable); - return pageEntity.map(mapper::toDto); + @Override + public LocationResponse findById(UUID id) { + LocationEntity entity = repository.findById(id) + .orElseThrow(() -> new RuntimeException("Location not found")); + return mapper.toResponse(entity); } + @Override + public Page findAll(int page, int size, String search, Boolean active) { + PageRequest pageable = PageRequest.of(page, size); + Page pageResult; + + if (search != null && !search.isEmpty() && active != null) { + pageResult = repository.findByNameContainingIgnoreCaseAndActive(search, active, pageable); + } else if (search != null && !search.isEmpty()) { + pageResult = repository.findByNameContainingIgnoreCase(search, pageable); + } else if (active != null) { + pageResult = active ? repository.findByActiveTrue(pageable) + : repository.findByActiveFalse(pageable); + } else { + pageResult = repository.findAll(pageable); + } + + return pageResult.map(mapper::toResponse); + } } + diff --git a/src/main/java/com/classes/services/Impl/TrainerServiceImpl.java b/src/main/java/com/classes/services/Impl/TrainerServiceImpl.java index a8175ef..973edba 100644 --- a/src/main/java/com/classes/services/Impl/TrainerServiceImpl.java +++ b/src/main/java/com/classes/services/Impl/TrainerServiceImpl.java @@ -1,14 +1,16 @@ package com.classes.services.Impl; -import com.classes.dtos.FileResponseDTO; -import com.classes.dtos.TrainerDTO; +import com.classes.dtos.Trainer.FileResponseDTO; +import com.classes.dtos.Trainer.TrainerDTO; import com.classes.entities.TrainerEntity; import com.classes.mappers.TrainerMapper; +import com.classes.repositories.ClassRepository; import com.classes.repositories.TrainerRepository; import com.classes.services.AzureService; import com.classes.services.CloudinaryService; import com.classes.services.TrainerService; import jakarta.persistence.EntityNotFoundException; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -26,7 +28,9 @@ public class TrainerServiceImpl implements TrainerService { private final TrainerMapper trainerMapper; private final CloudinaryService cloudinaryService; private final AzureService azureStorageService; + private final ClassRepository classRepository; + @Transactional @Override public TrainerDTO createTrainer(TrainerDTO trainerDTO, MultipartFile profileImage, @@ -49,6 +53,7 @@ public TrainerDTO createTrainer(TrainerDTO trainerDTO, return trainerMapper.toDTO(savedTrainer); } + @Transactional(readOnly = true) @Override public TrainerDTO getTrainerById(UUID id) { TrainerEntity trainer = trainerRepository.findById(id) @@ -56,11 +61,13 @@ public TrainerDTO getTrainerById(UUID id) { return trainerMapper.toDTO(trainer); } + @Transactional(readOnly = true) @Override public List getAllTrainers() { return trainerMapper.toDTOList(trainerRepository.findAll()); } + @Transactional @Override public TrainerDTO updateTrainer(UUID id, TrainerDTO trainerDTO, @@ -86,10 +93,18 @@ public TrainerDTO updateTrainer(UUID id, TrainerEntity updated = trainerRepository.save(trainer); return trainerMapper.toDTO(updated); } + @Transactional @Override public void deleteTrainer(UUID id) throws IOException { TrainerEntity trainer = trainerRepository.findById(id) .orElseThrow(() -> new EntityNotFoundException("Trainer not found with id: " + id)); + + boolean hasClasses = classRepository.findFirstByTrainerId(id).isPresent(); + if (hasClasses) { + throw new IllegalArgumentException( + "No se puede eliminar el trainer porque tiene clases asignadas" + ); + } if (trainer.getProfileImageUrl() != null) { cloudinaryService.delete(trainer.getProfileImageUrl()); } diff --git a/src/main/java/com/classes/services/LocationService.java b/src/main/java/com/classes/services/LocationService.java index 09921b9..5dad83b 100644 --- a/src/main/java/com/classes/services/LocationService.java +++ b/src/main/java/com/classes/services/LocationService.java @@ -1,17 +1,21 @@ package com.classes.services; -import com.classes.dtos.LocationDTO; -import com.classes.entities.LocationEntity; +import com.classes.dtos.Location.LocationRequest; +import com.classes.dtos.Location.LocationResponse; import org.springframework.data.domain.Page; -import java.util.List; import java.util.UUID; public interface LocationService { - LocationDTO create(LocationDTO dto); - LocationDTO update(UUID id, LocationDTO dto); + LocationResponse create(LocationRequest request); + + LocationResponse update(UUID id, LocationRequest request); + void delete(UUID id); - LocationEntity findById(UUID id); - Page findAll(int page, int size); -} + + LocationResponse findById(UUID id); + + // Paginación + filtros + Page findAll(int page, int size, String search, Boolean active); +} \ No newline at end of file diff --git a/src/main/java/com/classes/services/TrainerService.java b/src/main/java/com/classes/services/TrainerService.java index 1b93963..f2b6a44 100644 --- a/src/main/java/com/classes/services/TrainerService.java +++ b/src/main/java/com/classes/services/TrainerService.java @@ -1,6 +1,6 @@ package com.classes.services; -import com.classes.dtos.TrainerDTO; +import com.classes.dtos.Trainer.TrainerDTO; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -22,5 +22,9 @@ TrainerDTO updateTrainer(UUID id, List certifications) throws IOException; void deleteTrainer(UUID id) throws IOException; + + + + } diff --git a/src/main/java/com/classes/services/TrainerServiceImpl.java b/src/main/java/com/classes/services/TrainerServiceImpl.java deleted file mode 100644 index eb2a4a5..0000000 --- a/src/main/java/com/classes/services/TrainerServiceImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.classes.services; - -public class TrainerServiceImpl { -} diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml index 26449e8..3381ebb 100644 --- a/src/main/resources/bootstrap.yml +++ b/src/main/resources/bootstrap.yml @@ -6,4 +6,7 @@ spring: cloud: config: uri: ${SPRING_CLOUD_CONFIG_URI:http://config-server:7777} - \ No newline at end of file + servlet: + multipart: + max-file-size: 10MB + max-request-size: 20MB \ No newline at end of file