diff --git a/src/main/java/com/soupulsar/SouPulsarMonolithApplication.java b/src/main/java/com/soupulsar/SouPulsarMonolithApplication.java index 0bbc17c..3f11c68 100644 --- a/src/main/java/com/soupulsar/SouPulsarMonolithApplication.java +++ b/src/main/java/com/soupulsar/SouPulsarMonolithApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.web.config.EnableSpringDataWebSupport; +@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) @SpringBootApplication public class SouPulsarMonolithApplication { diff --git a/src/main/java/com/soupulsar/api/controllers/SpecialistsController.java b/src/main/java/com/soupulsar/api/controllers/SpecialistsController.java new file mode 100644 index 0000000..fae70ff --- /dev/null +++ b/src/main/java/com/soupulsar/api/controllers/SpecialistsController.java @@ -0,0 +1,51 @@ +package com.soupulsar.api.controllers; + +import com.soupulsar.application.dto.request.GetAllSpecialistRequest; +import com.soupulsar.application.dto.response.DailyAvailabilityResponse; +import com.soupulsar.application.dto.response.GetAllSpecialistsResponse; +import com.soupulsar.application.dto.response.SpecialistDetailsResponse; +import com.soupulsar.application.usecase.specialist.GetAllSpecialistsUseCase; +import com.soupulsar.application.usecase.specialist.GetDailyAvailabilityUseCase; +import com.soupulsar.application.usecase.specialist.GetSpecialistDetailsUseCase; +import com.soupulsar.domain.model.enums.SpecialistType; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; + +@RestController +@RequestMapping("/api/specialists") +@RequiredArgsConstructor +public class SpecialistsController { + + private final GetAllSpecialistsUseCase getAllSpecialistsUseCase; + private final GetSpecialistDetailsUseCase getSpecialistDetailsUseCase; + private final GetDailyAvailabilityUseCase getDailyAvailabilityUseCase; + + @GetMapping + public ResponseEntity> getAllSpecialists( + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer size, + @RequestParam(required = false) SpecialistType specialistType, + @RequestParam(required = false) BigDecimal minPrice, + @RequestParam(required = false) BigDecimal maxPrice + ) { + return ResponseEntity.ok(getAllSpecialistsUseCase.execute(GetAllSpecialistRequest.of(specialistType, minPrice, maxPrice, page, size))); + } + + @GetMapping("/{specialistId}") + public ResponseEntity getSpecialistDetails(@PathVariable UUID specialistId) { + var response = getSpecialistDetailsUseCase.execute(specialistId); + return ResponseEntity.ok(response); + } + + @GetMapping("/{specialistId}/availability/{date}") + public ResponseEntity getSpecialistDailyAvailability(@PathVariable UUID specialistId, @PathVariable LocalDate date) { + var response =getDailyAvailabilityUseCase.execute(specialistId, date); + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/api/controllers/UserController.java b/src/main/java/com/soupulsar/api/controllers/UserController.java new file mode 100644 index 0000000..40c64a3 --- /dev/null +++ b/src/main/java/com/soupulsar/api/controllers/UserController.java @@ -0,0 +1,65 @@ +package com.soupulsar.api.controllers; + +import com.soupulsar.application.dto.request.ChangePasswordRequest; +import com.soupulsar.application.dto.request.GetAllUsersRequest; +import com.soupulsar.application.dto.request.UpdateUserProfileRequest; +import com.soupulsar.application.dto.response.UserProfileResponse; +import com.soupulsar.application.dto.response.UserResponse; +import com.soupulsar.application.usecase.*; +import com.soupulsar.domain.model.enums.UserRole; +import com.soupulsar.domain.model.enums.UserStatus; +import com.soupulsar.domain.model.user.User; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@RestController +@RequestMapping("/api/users") +@RequiredArgsConstructor +public class UserController { + + private final GetAllUsersUseCase getAllUsersUseCase; + private final GetUserByIdUseCase getUserByIdUseCase; + private final GetUserProfileUseCase getUserProfileUseCase; + private final UpdateUserProfileUseCase updateUserProfileUseCase; + private final ChangePasswordUseCase changePasswordUseCase; + + @GetMapping("/{userId}") + public ResponseEntity getUserById(@PathVariable UUID userId) { + var response = getUserByIdUseCase.execute(userId); + return ResponseEntity.ok(response); + } + + @GetMapping + public ResponseEntity> getAllUsers( + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer size, + @RequestParam(required = false) UserStatus status, + @RequestParam(required = false) UserRole role + ) { + + return ResponseEntity.ok(getAllUsersUseCase.execute(GetAllUsersRequest.of(page, size, status, role))); + } + + @GetMapping("/me") + public ResponseEntity getProfile() { + var response = getUserProfileUseCase.execute(); + return ResponseEntity.ok(response); + + } + + @PatchMapping("/me") + public ResponseEntity updateProfile(@RequestBody UpdateUserProfileRequest request) { + updateUserProfileUseCase.execute(request); + return ResponseEntity.noContent().build(); + } + + @PutMapping("/me/password") + public ResponseEntity updatePassword(@RequestBody ChangePasswordRequest request) { + changePasswordUseCase.execute(request); + return ResponseEntity.ok().build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/config/UseCaseConfig.java b/src/main/java/com/soupulsar/application/config/UseCaseConfig.java index 704e695..df1b547 100644 --- a/src/main/java/com/soupulsar/application/config/UseCaseConfig.java +++ b/src/main/java/com/soupulsar/application/config/UseCaseConfig.java @@ -2,17 +2,21 @@ import com.soupulsar.application.security.JwtService; import com.soupulsar.application.security.PasswordHasher; -import com.soupulsar.application.usecase.AuthenticateUserUseCase; -import com.soupulsar.application.usecase.RegistrationUseCase; +import com.soupulsar.application.usecase.*; import com.soupulsar.application.usecase.availability.CreateAvailabilityUseCase; import com.soupulsar.application.usecase.session.CancelSessionUseCase; import com.soupulsar.application.usecase.session.CompleteSessionUseCase; import com.soupulsar.application.usecase.session.ConfirmSessionUseCase; import com.soupulsar.application.usecase.session.ScheduleSessionUseCase; +import com.soupulsar.application.usecase.specialist.GetAllSpecialistsUseCase; +import com.soupulsar.application.usecase.specialist.GetDailyAvailabilityUseCase; +import com.soupulsar.application.usecase.specialist.GetSpecialistDetailsUseCase; +import com.soupulsar.application.utils.SecurityUtils; import com.soupulsar.domain.repository.*; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class UseCaseConfig { @@ -51,4 +55,58 @@ public RegistrationUseCase registrationUseCase(UserRepository userRepository, Cl SpecialistProfileRepository specialistProfileRepository, PasswordHasher passwordHasher) { return new RegistrationUseCase(userRepository, clientProfileRepository, specialistProfileRepository,passwordHasher); } + @Bean + public GetAllUsersUseCase getAllUsersUseCase(UserRepository userRepository) { + return new GetAllUsersUseCase(userRepository); + } + + @Bean + public GetUserByIdUseCase getUserByIdUseCase(UserRepository userRepository) { + return new GetUserByIdUseCase(userRepository); + } + + @Bean + public GetAllSpecialistsUseCase getAllSpecialistsUseCase(SpecialistProfileRepository specialistProfileRepository, + UserRepository userRepository, + SessionRepository sessionRepository) { + return new GetAllSpecialistsUseCase(specialistProfileRepository, userRepository, sessionRepository); + } + + @Bean + GetSpecialistDetailsUseCase getSpecialistDetailsUseCase(SpecialistProfileRepository specialistRepository, + UserRepository userRepository, + SessionRepository sessionRepository) { + return new GetSpecialistDetailsUseCase(specialistRepository, userRepository, sessionRepository); + } + + @Bean + GetDailyAvailabilityUseCase getDailyAvailabilityUseCase(SessionRepository sessionRepository, + AvailabilityRepository availabilityRepository) { + return new GetDailyAvailabilityUseCase(sessionRepository, availabilityRepository); + } + + @Bean + public GetUserProfileUseCase getUserProfileUseCase(ClientProfileRepository clientProfileRepository, + SpecialistProfileRepository specialistProfileRepository, + UserRepository userRepository, + SecurityUtils securityUtils) { + return new GetUserProfileUseCase(clientProfileRepository, specialistProfileRepository, userRepository, securityUtils); + } + + @Bean + public UpdateUserProfileUseCase updateUserProfileUseCase(ClientProfileRepository clientProfileRepository, + UserRepository userRepository, + SecurityUtils securityUtils) { + return new UpdateUserProfileUseCase(userRepository, clientProfileRepository, securityUtils); + } + + @Bean + public SecurityUtils securityUtils(UserRepository userRepository) { + return new SecurityUtils(userRepository); + } + + @Bean + public ChangePasswordUseCase changePasswordUseCase(UserRepository userRepository, PasswordEncoder passwordEncoder, SecurityUtils securityUtils) { + return new ChangePasswordUseCase(userRepository, passwordEncoder, securityUtils); + } } \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/request/ChangePasswordRequest.java b/src/main/java/com/soupulsar/application/dto/request/ChangePasswordRequest.java new file mode 100644 index 0000000..d5e1bd2 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/request/ChangePasswordRequest.java @@ -0,0 +1,7 @@ +package com.soupulsar.application.dto.request; + +public record ChangePasswordRequest( + String currentPassword, + String newPassword +) { +} diff --git a/src/main/java/com/soupulsar/application/dto/request/GetAllSpecialistRequest.java b/src/main/java/com/soupulsar/application/dto/request/GetAllSpecialistRequest.java new file mode 100644 index 0000000..6417952 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/request/GetAllSpecialistRequest.java @@ -0,0 +1,26 @@ +package com.soupulsar.application.dto.request; + +import com.soupulsar.domain.model.enums.SpecialistType; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.math.BigDecimal; + +public record GetAllSpecialistRequest( + SpecialistType specialistType, + BigDecimal minPrice, + BigDecimal maxPrice, + Integer page, + Integer size +) { + + public static GetAllSpecialistRequest of(SpecialistType specialistType, BigDecimal minPrice, BigDecimal maxPrice, Integer page, Integer size) { + return new GetAllSpecialistRequest(specialistType, minPrice, maxPrice, page, size); + } + + public Pageable toPageable() { + int pageNumber = (page != null && page > 0) ? page : 0; + int pageSize = (size != null && size > 0) ? size : 10; + return PageRequest.of(pageNumber, pageSize); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/request/GetAllUsersRequest.java b/src/main/java/com/soupulsar/application/dto/request/GetAllUsersRequest.java new file mode 100644 index 0000000..a004baa --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/request/GetAllUsersRequest.java @@ -0,0 +1,26 @@ +package com.soupulsar.application.dto.request; + + +import com.soupulsar.domain.model.enums.UserRole; +import com.soupulsar.domain.model.enums.UserStatus; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record GetAllUsersRequest( + + Integer page, + Integer size, + UserStatus status, + UserRole role +) { + + public static GetAllUsersRequest of(Integer page, Integer size, UserStatus status, UserRole role) { + return new GetAllUsersRequest(page, size, status, role); + } + + public Pageable toPageable(){ + int pageNumber = (page != null && page > 0) ? page : 0; + int pageSize = (size != null && size > 0) ? size : 10; + return PageRequest.of(pageNumber, pageSize); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/request/RegistrationRequest.java b/src/main/java/com/soupulsar/application/dto/request/RegistrationRequest.java index 3f015a4..0f9091a 100644 --- a/src/main/java/com/soupulsar/application/dto/request/RegistrationRequest.java +++ b/src/main/java/com/soupulsar/application/dto/request/RegistrationRequest.java @@ -1,5 +1,6 @@ package com.soupulsar.application.dto.request; +import com.soupulsar.domain.model.enums.SpecialistType; import com.soupulsar.domain.model.enums.UserRole; import com.soupulsar.domain.model.vo.Address; import com.soupulsar.domain.model.vo.EmergencyContact; @@ -8,6 +9,7 @@ import jakarta.validation.constraints.NotNull; import org.hibernate.validator.constraints.br.CPF; +import java.math.BigDecimal; import java.util.Date; import java.util.List; @@ -35,6 +37,8 @@ public record RegistrationRequest( EmergencyContact emergencyContact, // Specialist info + SpecialistType specialistType, + BigDecimal sessionPrice, String registrationNumber, Presentation presentation, List formations, diff --git a/src/main/java/com/soupulsar/application/dto/request/UpdateUserProfileRequest.java b/src/main/java/com/soupulsar/application/dto/request/UpdateUserProfileRequest.java new file mode 100644 index 0000000..17685bb --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/request/UpdateUserProfileRequest.java @@ -0,0 +1,16 @@ +package com.soupulsar.application.dto.request; + +import com.soupulsar.domain.model.vo.Address; +import com.soupulsar.domain.model.vo.EmergencyContact; +import jakarta.validation.constraints.Email; + +public record UpdateUserProfileRequest( + + String name, + String phone, + Address address, + EmergencyContact emergencyContact, + @Email + String email +) { +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/response/AddressResponse.java b/src/main/java/com/soupulsar/application/dto/response/AddressResponse.java new file mode 100644 index 0000000..16f7fb9 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/AddressResponse.java @@ -0,0 +1,26 @@ +package com.soupulsar.application.dto.response; + +import com.soupulsar.domain.model.vo.Address; +import lombok.Builder; + +@Builder +public record AddressResponse( + + String street, + String city, + String state, + String zipcode, + String neighbourhood + +) { + + public static AddressResponse toResponse(Address address) { + return AddressResponse.builder() + .street(address.getStreet()) + .city(address.getCity()) + .state(address.getState()) + .zipcode(address.getZipCode()) + .neighbourhood(address.getNeighbourhood()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/response/ClientProfileResponse.java b/src/main/java/com/soupulsar/application/dto/response/ClientProfileResponse.java new file mode 100644 index 0000000..ae6e479 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/ClientProfileResponse.java @@ -0,0 +1,17 @@ +package com.soupulsar.application.dto.response; + +import com.soupulsar.domain.model.vo.EmergencyContact; +import lombok.Builder; + +import java.util.Date; + +@Builder +public record ClientProfileResponse( + String name, + String email, + String telephone, + Date birthday, + EmergencyContact emergencyContact, + AddressResponse addressResponse +) implements UserProfileResponse { +} diff --git a/src/main/java/com/soupulsar/application/dto/response/DailyAvailabilityResponse.java b/src/main/java/com/soupulsar/application/dto/response/DailyAvailabilityResponse.java new file mode 100644 index 0000000..1007b57 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/DailyAvailabilityResponse.java @@ -0,0 +1,10 @@ +package com.soupulsar.application.dto.response; + +import java.time.LocalDate; +import java.util.List; + +public record DailyAvailabilityResponse( + LocalDate date, + List slots +) { +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/response/GetAllSpecialistsResponse.java b/src/main/java/com/soupulsar/application/dto/response/GetAllSpecialistsResponse.java new file mode 100644 index 0000000..9b4cdd3 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/GetAllSpecialistsResponse.java @@ -0,0 +1,21 @@ +package com.soupulsar.application.dto.response; + +import com.soupulsar.domain.model.enums.SpecialistType; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +public record GetAllSpecialistsResponse( + UUID id, + String name, + String city, + String state, + List specialties, + List approaches, + String base64Image, + SpecialistType specialistType, + BigDecimal price, + Long sessionsCompleted +) { +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/response/GetSpecialistById.java b/src/main/java/com/soupulsar/application/dto/response/GetSpecialistById.java new file mode 100644 index 0000000..ee5f813 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/GetSpecialistById.java @@ -0,0 +1,25 @@ +package com.soupulsar.application.dto.response; + +import com.soupulsar.domain.model.enums.SpecialistType; + +import java.math.BigDecimal; +import java.util.List; + +public record GetSpecialistById( + String name, + String registrationNumber, + String city, + String state, + String about, + SpecialistType specialistType, + String base64Image, + Long sessionsCompleted, + BigDecimal price, + String presentationVideoUrl, + String personalDescription, + List approaches, + List specialties, + List formations + +) { +} diff --git a/src/main/java/com/soupulsar/application/dto/response/SpecialistAvailabilityResponse.java b/src/main/java/com/soupulsar/application/dto/response/SpecialistAvailabilityResponse.java new file mode 100644 index 0000000..e622ba0 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/SpecialistAvailabilityResponse.java @@ -0,0 +1,6 @@ +package com.soupulsar.application.dto.response; + +public record SpecialistAvailabilityResponse( + +) { +} diff --git a/src/main/java/com/soupulsar/application/dto/response/SpecialistDetailsResponse.java b/src/main/java/com/soupulsar/application/dto/response/SpecialistDetailsResponse.java new file mode 100644 index 0000000..1973072 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/SpecialistDetailsResponse.java @@ -0,0 +1,28 @@ +package com.soupulsar.application.dto.response; + +import com.soupulsar.domain.model.enums.SpecialistType; +import lombok.Builder; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +@Builder +public record SpecialistDetailsResponse( + UUID id, + String name, + String registrationNumber, + String city, + String state, + String about, + SpecialistType specialistType, + String introductionVideoUrl, + String base64Image, + Long sessionsCompleted, + BigDecimal price, + String personalDescription, + List specialties, + List approaches, + List formations +) { +} diff --git a/src/main/java/com/soupulsar/application/dto/response/SpecialistProfileResponse.java b/src/main/java/com/soupulsar/application/dto/response/SpecialistProfileResponse.java new file mode 100644 index 0000000..1f9ee50 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/SpecialistProfileResponse.java @@ -0,0 +1,19 @@ +package com.soupulsar.application.dto.response; + +import com.soupulsar.domain.model.enums.SpecialistType; +import lombok.Builder; + +import java.util.List; + +@Builder +public record SpecialistProfileResponse( + String name, + String email, + String telephone, + SpecialistType specialistType, + List therapeuticApproaches, + List clinicalSpecialties, + List education, + AddressResponse addressResponse +) implements UserProfileResponse { +} diff --git a/src/main/java/com/soupulsar/application/dto/response/TimeSlot.java b/src/main/java/com/soupulsar/application/dto/response/TimeSlot.java new file mode 100644 index 0000000..b88eec3 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/TimeSlot.java @@ -0,0 +1,9 @@ +package com.soupulsar.application.dto.response; + +import java.time.LocalTime; + +public record TimeSlot( + LocalTime start, + LocalTime end, + boolean isBooked +){} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/response/UserProfileResponse.java b/src/main/java/com/soupulsar/application/dto/response/UserProfileResponse.java new file mode 100644 index 0000000..d606055 --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/UserProfileResponse.java @@ -0,0 +1,3 @@ +package com.soupulsar.application.dto.response; + +public sealed interface UserProfileResponse permits ClientProfileResponse, SpecialistProfileResponse {} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/dto/response/UserResponse.java b/src/main/java/com/soupulsar/application/dto/response/UserResponse.java new file mode 100644 index 0000000..17e791b --- /dev/null +++ b/src/main/java/com/soupulsar/application/dto/response/UserResponse.java @@ -0,0 +1,35 @@ +package com.soupulsar.application.dto.response; + + + +import com.soupulsar.domain.model.enums.UserRole; +import com.soupulsar.domain.model.enums.UserStatus; +import com.soupulsar.domain.model.user.User; + +import java.util.UUID; + +public record UserResponse( + + UUID userId, + String name, + String cpf, + String email, + String telephone, + UserRole role, + UserStatus status, + AddressResponse address +) { + + public UserResponse(User response){ + this( + response.getUserId(), + response.getName(), + response.getCpf(), + response.getEmail(), + response.getTelephone(), + response.getRole(), + response.getStatus(), + AddressResponse.toResponse(response.getAddress()) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/exceptions/InvalidUserException.java b/src/main/java/com/soupulsar/application/exceptions/InvalidUserException.java new file mode 100644 index 0000000..07e6144 --- /dev/null +++ b/src/main/java/com/soupulsar/application/exceptions/InvalidUserException.java @@ -0,0 +1,7 @@ +package com.soupulsar.application.exceptions; + +public class InvalidUserException extends RuntimeException { + public InvalidUserException(String message) { + super(message); + } +} diff --git a/src/main/java/com/soupulsar/application/exceptions/UserNotAuthenticatedException.java b/src/main/java/com/soupulsar/application/exceptions/UserNotAuthenticatedException.java new file mode 100644 index 0000000..3c480dc --- /dev/null +++ b/src/main/java/com/soupulsar/application/exceptions/UserNotAuthenticatedException.java @@ -0,0 +1,7 @@ +package com.soupulsar.application.exceptions; + +public class UserNotAuthenticatedException extends RuntimeException { + public UserNotAuthenticatedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/soupulsar/application/security/JwtService.java b/src/main/java/com/soupulsar/application/security/JwtService.java index f20fbaa..4c144b3 100644 --- a/src/main/java/com/soupulsar/application/security/JwtService.java +++ b/src/main/java/com/soupulsar/application/security/JwtService.java @@ -41,8 +41,8 @@ public String generateToken(User user) { Date expiryDate = new Date(issueDate.getTime() + expirationMs); return Jwts.builder() - .subject(user.getEmail()) - .claim("userId", user.getUserId().toString()) + .subject(user.getUserId().toString()) + .claim("email", user.getEmail()) .claim("role", user.getRole()) .claim("status", user.getStatus()) .issuer("SouPulsar-AuthService") diff --git a/src/main/java/com/soupulsar/application/usecase/AuthenticateUserUseCase.java b/src/main/java/com/soupulsar/application/usecase/AuthenticateUserUseCase.java index 65b5927..f2c88ce 100644 --- a/src/main/java/com/soupulsar/application/usecase/AuthenticateUserUseCase.java +++ b/src/main/java/com/soupulsar/application/usecase/AuthenticateUserUseCase.java @@ -4,6 +4,7 @@ import com.soupulsar.application.dto.response.AuthUserResponse; import com.soupulsar.application.security.JwtService; import com.soupulsar.application.security.PasswordHasher; +import com.soupulsar.domain.exceptions.UserNotFoundException; import com.soupulsar.domain.model.user.User; import com.soupulsar.domain.model.enums.UserStatus; import com.soupulsar.domain.repository.UserRepository; @@ -22,7 +23,7 @@ public class AuthenticateUserUseCase { public AuthUserResponse execute(AuthUserRequest request) { User user = userRepository.findByEmail(request.email()) - .orElseThrow(() -> new IllegalArgumentException("Invalid email or password")); + .orElseThrow(UserNotFoundException::new); if (user.getStatus() != UserStatus.ACTIVE) { throw new IllegalArgumentException("User is not active"); diff --git a/src/main/java/com/soupulsar/application/usecase/ChangePasswordUseCase.java b/src/main/java/com/soupulsar/application/usecase/ChangePasswordUseCase.java new file mode 100644 index 0000000..3efd35c --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/ChangePasswordUseCase.java @@ -0,0 +1,33 @@ +package com.soupulsar.application.usecase; + +import com.soupulsar.application.dto.request.ChangePasswordRequest; +import com.soupulsar.application.utils.SecurityUtils; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@RequiredArgsConstructor +public class ChangePasswordUseCase { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final SecurityUtils securityUtils; + + @Transactional + public void execute(ChangePasswordRequest request){ + + var user = securityUtils.getCurrentUser(); + + if (!passwordEncoder.matches(request.currentPassword(), user.getPasswordHash())) { + throw new IllegalArgumentException("Current password is incorrect"); + } + + user.setPasswordHash(passwordEncoder.encode(request.newPassword())); + + userRepository.save(user); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/GetAllUsersUseCase.java b/src/main/java/com/soupulsar/application/usecase/GetAllUsersUseCase.java new file mode 100644 index 0000000..56cdaf4 --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/GetAllUsersUseCase.java @@ -0,0 +1,19 @@ +package com.soupulsar.application.usecase; + +import com.soupulsar.application.dto.request.GetAllUsersRequest; +import com.soupulsar.domain.model.user.User; +import com.soupulsar.domain.repository.UserRepository; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; + +@RequiredArgsConstructor +@Builder +public class GetAllUsersUseCase { + + private final UserRepository userRepository; + + public Page execute(GetAllUsersRequest request) { + return userRepository.findAll(request.status(), request.role(), request.toPageable()); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/GetUserByIdUseCase.java b/src/main/java/com/soupulsar/application/usecase/GetUserByIdUseCase.java new file mode 100644 index 0000000..fee70f7 --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/GetUserByIdUseCase.java @@ -0,0 +1,19 @@ +package com.soupulsar.application.usecase; + +import com.soupulsar.application.dto.response.UserResponse; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor +public class GetUserByIdUseCase { + + private final UserRepository userRepository; + + public UserResponse execute(UUID userId) { + + return new UserResponse(userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found"))); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/GetUserProfileUseCase.java b/src/main/java/com/soupulsar/application/usecase/GetUserProfileUseCase.java new file mode 100644 index 0000000..62f8502 --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/GetUserProfileUseCase.java @@ -0,0 +1,74 @@ +package com.soupulsar.application.usecase; + +import com.soupulsar.application.dto.response.AddressResponse; +import com.soupulsar.application.dto.response.ClientProfileResponse; +import com.soupulsar.application.dto.response.SpecialistProfileResponse; +import com.soupulsar.application.dto.response.UserProfileResponse; +import com.soupulsar.application.utils.SecurityUtils; +import com.soupulsar.domain.model.client.ClientProfile; +import com.soupulsar.domain.model.specialist.SpecialistProfile; +import com.soupulsar.domain.model.user.User; +import com.soupulsar.domain.repository.ClientProfileRepository; +import com.soupulsar.domain.repository.SpecialistProfileRepository; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.UUID; + +@RequiredArgsConstructor +public class GetUserProfileUseCase { + + private final ClientProfileRepository clientProfileRepository; + private final SpecialistProfileRepository specialistProfileRepository; + private final UserRepository userRepository; + private final SecurityUtils securityUtils; + + + public UserProfileResponse execute(){ + + UUID userId = securityUtils.getCurrentUserId(); + + User user = userRepository.findById(userId).orElseThrow(() -> new UsernameNotFoundException("User not found")); + + return switch (user.getRole()){ + case CLIENT -> buildClientResponse(userId, user); + case SPECIALIST -> buildSpecialistResponse(userId, user); + case ADMIN -> throw new UnsupportedOperationException("Admin profile retrieval is not supported"); + }; + + } + + private SpecialistProfileResponse buildSpecialistResponse(UUID userId, User user){ + SpecialistProfile specialistProfile = specialistProfileRepository.findById(userId). + orElseThrow(() -> new UsernameNotFoundException("Specialist not found with id: " + user.getUserId())); + + return SpecialistProfileResponse.builder() + .name(user.getName()) + .email(user.getEmail()) + .telephone(user.getTelephone()) + .specialistType(specialistProfile.getSpecialistType()) + .therapeuticApproaches(specialistProfile.getApproaches()) + .clinicalSpecialties(specialistProfile.getSpecialties()) + .education(specialistProfile.getFormations()) + .addressResponse(AddressResponse.toResponse(user.getAddress())) + .build(); + } + + + private ClientProfileResponse buildClientResponse(UUID userId, User user) { + ClientProfile clientProfile = clientProfileRepository.findById(userId). + orElseThrow(() -> new UsernameNotFoundException("Client not found with id: " + user.getUserId())); + + return ClientProfileResponse.builder() + .name(user.getName()) + .email(user.getEmail()) + .telephone(user.getTelephone()) + .birthday(clientProfile.getDateOfBirth()) + .addressResponse(AddressResponse.toResponse(user.getAddress())) + .emergencyContact(clientProfile.getEmergencyContact()) + .build(); + + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/RegistrationUseCase.java b/src/main/java/com/soupulsar/application/usecase/RegistrationUseCase.java index 7acd3da..870d3be 100644 --- a/src/main/java/com/soupulsar/application/usecase/RegistrationUseCase.java +++ b/src/main/java/com/soupulsar/application/usecase/RegistrationUseCase.java @@ -55,7 +55,9 @@ public RegistrationResponse execute(RegistrationRequest request) { request.presentation(), request.formations(), request.specialties(), - request.approaches() + request.approaches(), + request.specialistType(), + request.sessionPrice() ); specialistProfileRepository.save(specialistProfile); } else { diff --git a/src/main/java/com/soupulsar/application/usecase/UpdateUserProfileUseCase.java b/src/main/java/com/soupulsar/application/usecase/UpdateUserProfileUseCase.java new file mode 100644 index 0000000..11871bd --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/UpdateUserProfileUseCase.java @@ -0,0 +1,39 @@ +package com.soupulsar.application.usecase; + +import com.soupulsar.application.dto.request.UpdateUserProfileRequest; +import com.soupulsar.application.utils.SecurityUtils; +import com.soupulsar.domain.repository.ClientProfileRepository; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +public class UpdateUserProfileUseCase { + + + private final UserRepository userRepository; + private final ClientProfileRepository clientProfileRepository; + private final SecurityUtils securityUtils; + + @Transactional + public void execute(UpdateUserProfileRequest request){ + + var user = securityUtils.getCurrentUser(); + + var clientProfile = clientProfileRepository.findById(user.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("Client profile not found")); + + if (request.email() != null && !request.email().equals(user.getEmail()) && userRepository.existsByEmail(request.email())) { + throw new IllegalArgumentException("Email already exists"); + } + user.setEmail(request.email()); + user.setAddress(request.address() != null ? request.address() : user.getAddress()); + user.setName(request.name() != null ? request.name() : user.getName()); + user.setTelephone(request.phone() != null ? request.phone() : user.getTelephone()); + + clientProfile.setEmergencyContact(request.emergencyContact() != null ? request.emergencyContact() : clientProfile.getEmergencyContact()); + + userRepository.save(user); + clientProfileRepository.save(clientProfile); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/specialist/GetAllSpecialistsUseCase.java b/src/main/java/com/soupulsar/application/usecase/specialist/GetAllSpecialistsUseCase.java new file mode 100644 index 0000000..c362f16 --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/specialist/GetAllSpecialistsUseCase.java @@ -0,0 +1,62 @@ +package com.soupulsar.application.usecase.specialist; + +import com.soupulsar.application.dto.request.GetAllSpecialistRequest; +import com.soupulsar.application.dto.response.GetAllSpecialistsResponse; +import com.soupulsar.domain.model.specialist.SpecialistProfile; +import com.soupulsar.domain.model.user.User; +import com.soupulsar.domain.repository.SessionRepository; +import com.soupulsar.domain.repository.SpecialistProfileRepository; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@RequiredArgsConstructor +public class GetAllSpecialistsUseCase { + + private final SpecialistProfileRepository specialistProfileRepository; + private final UserRepository userRepository; + private final SessionRepository sessionRepository; + + + public Page execute(GetAllSpecialistRequest request) { + + Page specialistProfiles = + specialistProfileRepository.findAll(request.specialistType(), request.minPrice(), request.maxPrice(), request.toPageable()); + + + List userIds = specialistProfiles + .getContent() + .stream() + .map(SpecialistProfile::getUserId) + .toList(); + + Map userMap = userRepository.findAllByUserId(userIds); + + Map sessionCompletedCountMap = + sessionRepository.countCompletedSessionsBySpecialistIds(userIds); + + return specialistProfiles.map(specialist ->{ + User user = userMap.get(specialist.getUserId()); + + return new GetAllSpecialistsResponse( + specialist.getUserId(), + user.getName(), + user.getAddress().getCity(), + user.getAddress().getState(), + specialist.getSpecialties(), + specialist.getApproaches(), + specialist.getPresentation().getBase64Image(), + specialist.getSpecialistType(), + specialist.getSessionPrice(), + sessionCompletedCountMap.getOrDefault(specialist.getUserId(), 0L) + ); + + }); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/specialist/GetDailyAvailabilityUseCase.java b/src/main/java/com/soupulsar/application/usecase/specialist/GetDailyAvailabilityUseCase.java new file mode 100644 index 0000000..3cfdd3e --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/specialist/GetDailyAvailabilityUseCase.java @@ -0,0 +1,70 @@ +package com.soupulsar.application.usecase.specialist; + +import com.soupulsar.application.dto.response.DailyAvailabilityResponse; +import com.soupulsar.application.dto.response.TimeSlot; +import com.soupulsar.domain.model.availability.Availability; +import com.soupulsar.domain.model.session.Session; +import com.soupulsar.domain.repository.AvailabilityRepository; +import com.soupulsar.domain.repository.SessionRepository; +import lombok.RequiredArgsConstructor; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +public class GetDailyAvailabilityUseCase { + + private static final Duration SESSION_DURATION = Duration.ofMinutes(50); + private static final Duration BREAK_INTERVAL = Duration.ofMinutes(10); + + private final SessionRepository sessionRepository; + private final AvailabilityRepository availabilityRepository; + + public DailyAvailabilityResponse execute(UUID specialistId, LocalDate date) { + + List availabilities = availabilityRepository.findBySpecialistIdAndDayOfWeek(specialistId, date.getDayOfWeek()); + if (availabilities.isEmpty()) return new DailyAvailabilityResponse(date, List.of()); + + + List sessions = sessionRepository.findBySpecialistIdAndDate(specialistId, date); + + List timeSlots = generateTimeSlots(availabilities, sessions); + + return new DailyAvailabilityResponse(date, timeSlots); + } + + + private List generateTimeSlots(List availabilities, List sessions) { + List timeSlots = new ArrayList<>(); + + for (Availability availability : availabilities) { + LocalTime current = availability.getStartTime(); + + while (!current.plus(SESSION_DURATION).isAfter(availability.getEndTime())) { + LocalTime slotStart = current; + LocalTime slotEnd = current.plus(SESSION_DURATION); + + boolean isBooked = sessions.stream().anyMatch(session -> { + LocalTime sessionStart = session.getStartAt().toLocalTime(); + LocalTime sessionEnd = session.getEndAt().toLocalTime(); + + LocalTime sessionStartWithBuffer = sessionStart.minus(BREAK_INTERVAL); + LocalTime sessionEndWithBuffer = sessionEnd.plus(BREAK_INTERVAL); + + return slotStart.isBefore(sessionEndWithBuffer) && slotEnd.isAfter(sessionStartWithBuffer); + }); + + timeSlots.add(new TimeSlot(slotStart, slotEnd, isBooked)); + + current = slotEnd.plus(BREAK_INTERVAL); + } + } + + return timeSlots; + } + +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/usecase/specialist/GetSpecialistDetailsUseCase.java b/src/main/java/com/soupulsar/application/usecase/specialist/GetSpecialistDetailsUseCase.java new file mode 100644 index 0000000..abc468a --- /dev/null +++ b/src/main/java/com/soupulsar/application/usecase/specialist/GetSpecialistDetailsUseCase.java @@ -0,0 +1,46 @@ +package com.soupulsar.application.usecase.specialist; + +import com.soupulsar.application.dto.response.SpecialistDetailsResponse; +import com.soupulsar.domain.repository.SessionRepository; +import com.soupulsar.domain.repository.SpecialistProfileRepository; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor +public class GetSpecialistDetailsUseCase { + + private final SpecialistProfileRepository specialistRepository; + private final UserRepository userRepository; + private final SessionRepository sessionRepository; + + public SpecialistDetailsResponse execute(UUID specialistId) { + + var specialistProfile = specialistRepository.findById(specialistId) + .orElseThrow(() -> new IllegalArgumentException("Specialist profile not found")); + + var user = userRepository.findById(specialistProfile.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + + Long completedSessions = sessionRepository.countCompletedSessionsBySpecialistId(specialistId); + + return SpecialistDetailsResponse.builder() + .id(specialistProfile.getUserId()) + .name(user.getName()) + .registrationNumber(specialistProfile.getRegistrationNumber()) + .city(user.getAddress().getCity()) + .state(user.getAddress().getState()) + .about(specialistProfile.getPresentation().getAbout()) + .specialistType(specialistProfile.getSpecialistType()) + .introductionVideoUrl(specialistProfile.getPresentation().getPresentationVideoUrl()) + .base64Image(specialistProfile.getPresentation().getBase64Image()) + .sessionsCompleted(completedSessions) + .price(specialistProfile.getSessionPrice()) + .personalDescription(specialistProfile.getPresentation().getPersonalDescription()) + .specialties(specialistProfile.getSpecialties()) + .approaches(specialistProfile.getApproaches()) + .formations(specialistProfile.getFormations()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/application/utils/SecurityUtils.java b/src/main/java/com/soupulsar/application/utils/SecurityUtils.java new file mode 100644 index 0000000..7710614 --- /dev/null +++ b/src/main/java/com/soupulsar/application/utils/SecurityUtils.java @@ -0,0 +1,35 @@ +package com.soupulsar.application.utils; + +import com.soupulsar.application.exceptions.InvalidUserException; +import com.soupulsar.application.exceptions.UserNotAuthenticatedException; +import com.soupulsar.domain.exceptions.UserNotFoundException; +import com.soupulsar.domain.model.user.User; +import com.soupulsar.domain.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.UUID; + +@RequiredArgsConstructor +public class SecurityUtils { + + private final UserRepository userRepository; + + public UUID getCurrentUserId() { + var auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null || !auth.isAuthenticated()) { + throw new UserNotAuthenticatedException("No authenticated user found"); + } + try { + return UUID.fromString(auth.getName()); + } catch (Exception e) { + throw new InvalidUserException("Invalid user ID in authentication context"); + } + } + + public User getCurrentUser() { + UUID userId = getCurrentUserId(); + return userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(userId)); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/domain/exceptions/UserNotFoundException.java b/src/main/java/com/soupulsar/domain/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..b8cfbed --- /dev/null +++ b/src/main/java/com/soupulsar/domain/exceptions/UserNotFoundException.java @@ -0,0 +1,13 @@ +package com.soupulsar.domain.exceptions; + +import java.util.UUID; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(UUID userId) { + super("User not found with ID: " + userId); + } + public UserNotFoundException() { + super("User not found"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/domain/model/client/ClientProfile.java b/src/main/java/com/soupulsar/domain/model/client/ClientProfile.java index 3944860..4e98b51 100644 --- a/src/main/java/com/soupulsar/domain/model/client/ClientProfile.java +++ b/src/main/java/com/soupulsar/domain/model/client/ClientProfile.java @@ -1,23 +1,21 @@ package com.soupulsar.domain.model.client; import com.soupulsar.domain.model.vo.EmergencyContact; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; +import lombok.*; import java.util.Date; import java.util.UUID; @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter +@Setter @Builder public class ClientProfile { private final UUID profileId; private final UUID userId; private final Date dateOfBirth; - private final EmergencyContact emergencyContact; + private EmergencyContact emergencyContact; public static ClientProfile create(UUID userId, Date dateOfBirth, EmergencyContact emergencyContact) { diff --git a/src/main/java/com/soupulsar/domain/model/enums/RelationshipDegree.java b/src/main/java/com/soupulsar/domain/model/enums/RelationshipDegree.java index 1f1a137..a298d82 100644 --- a/src/main/java/com/soupulsar/domain/model/enums/RelationshipDegree.java +++ b/src/main/java/com/soupulsar/domain/model/enums/RelationshipDegree.java @@ -1,5 +1,7 @@ package com.soupulsar.domain.model.enums; +import com.fasterxml.jackson.annotation.JsonCreator; + public enum RelationshipDegree { CONJUGE, @@ -17,5 +19,15 @@ public enum RelationshipDegree { SOGRO, AVO, COMPANHEIRO, - OUTRO + OUTRO; + + @JsonCreator + public static RelationshipDegree fromString(String value) { + for (RelationshipDegree degree : RelationshipDegree.values()) { + if (degree.name().equalsIgnoreCase(value)) { + return degree; + } + } + throw new IllegalArgumentException("Unknown RelationshipDegree: " + value); + } } \ No newline at end of file diff --git a/src/main/java/com/soupulsar/domain/model/enums/SpecialistType.java b/src/main/java/com/soupulsar/domain/model/enums/SpecialistType.java new file mode 100644 index 0000000..a14beea --- /dev/null +++ b/src/main/java/com/soupulsar/domain/model/enums/SpecialistType.java @@ -0,0 +1,23 @@ +package com.soupulsar.domain.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum SpecialistType { + + PSICOLOGO, + PSIQUIATRA, + TERAPEUTA, + EDUCADOR_FISICO, + NUTRICIONISTA, + ASSESSOR_FINANCEIRO; + + @JsonCreator + public static SpecialistType fromString(String value) { + for (SpecialistType type : SpecialistType.values()) { + if (type.name().equalsIgnoreCase(value)) { + return type; + } + } + throw new IllegalArgumentException("Unknown SpecialistType: " + value); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/domain/model/enums/UserRole.java b/src/main/java/com/soupulsar/domain/model/enums/UserRole.java index a8851c6..056a0bf 100644 --- a/src/main/java/com/soupulsar/domain/model/enums/UserRole.java +++ b/src/main/java/com/soupulsar/domain/model/enums/UserRole.java @@ -1,8 +1,20 @@ package com.soupulsar.domain.model.enums; +import com.fasterxml.jackson.annotation.JsonCreator; + public enum UserRole { CLIENT, SPECIALIST, - ADMIN + ADMIN; + + @JsonCreator + public static UserRole fromString(String value) { + for (UserRole role : UserRole.values()) { + if (role.name().equalsIgnoreCase(value)) { + return role; + } + } + throw new IllegalArgumentException("Unknown UserRole: " + value); + } } \ No newline at end of file diff --git a/src/main/java/com/soupulsar/domain/model/specialist/SpecialistProfile.java b/src/main/java/com/soupulsar/domain/model/specialist/SpecialistProfile.java index 35a7e2d..1c80c27 100644 --- a/src/main/java/com/soupulsar/domain/model/specialist/SpecialistProfile.java +++ b/src/main/java/com/soupulsar/domain/model/specialist/SpecialistProfile.java @@ -1,11 +1,13 @@ package com.soupulsar.domain.model.specialist; +import com.soupulsar.domain.model.enums.SpecialistType; import com.soupulsar.domain.model.vo.Presentation; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.Singular; +import java.math.BigDecimal; import java.util.List; import java.util.UUID; @@ -17,6 +19,8 @@ public class SpecialistProfile { private final UUID profileId; private final UUID userId; private final String registrationNumber; + private final SpecialistType specialistType; + private BigDecimal sessionPrice; private Presentation presentation; @Singular("formation") private List formations; @@ -26,7 +30,8 @@ public class SpecialistProfile { private List approaches; - public static SpecialistProfile create(UUID userId, String registrationNumber, Presentation presentation, List formation, List specialties, List approaches) { + public static SpecialistProfile create(UUID userId, String registrationNumber, Presentation presentation, List formation, + List specialties, List approaches, SpecialistType specialistType, BigDecimal sessionPrice) { if (userId == null) throw new IllegalArgumentException("User ID cannot be null"); return SpecialistProfile.builder() .profileId(UUID.randomUUID()) @@ -34,6 +39,8 @@ public static SpecialistProfile create(UUID userId, String registrationNumber, P .registrationNumber(registrationNumber) .presentation(presentation) .formations(formation) + .specialistType(specialistType) + .sessionPrice(sessionPrice) .specialties(specialties) .approaches(approaches) .build(); diff --git a/src/main/java/com/soupulsar/domain/model/user/User.java b/src/main/java/com/soupulsar/domain/model/user/User.java index 05f6298..f7b7b7d 100644 --- a/src/main/java/com/soupulsar/domain/model/user/User.java +++ b/src/main/java/com/soupulsar/domain/model/user/User.java @@ -3,27 +3,25 @@ import com.soupulsar.domain.model.enums.UserRole; import com.soupulsar.domain.model.enums.UserStatus; import com.soupulsar.domain.model.vo.Address; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; +import lombok.*; import java.util.UUID; @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter +@Setter @Builder public class User { private final UUID userId; - private final String name; + private String name; private final String cpf; private String telephone; private String email; private String passwordHash; private Address address; private final UserRole role; - private UserStatus status; + private UserStatus status; public static User create(String name, String cpf, String telephone, String email, String passwordHash, UserRole role, Address address) { diff --git a/src/main/java/com/soupulsar/domain/repository/SessionRepository.java b/src/main/java/com/soupulsar/domain/repository/SessionRepository.java index c906ef8..ac5e745 100644 --- a/src/main/java/com/soupulsar/domain/repository/SessionRepository.java +++ b/src/main/java/com/soupulsar/domain/repository/SessionRepository.java @@ -2,13 +2,21 @@ import com.soupulsar.domain.model.session.Session; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; public interface SessionRepository { + Map countCompletedSessionsBySpecialistIds(List specialistIds); + + Long countCompletedSessionsBySpecialistId(UUID specialistId); + + List findBySpecialistIdAndDate(UUID specialistId, LocalDate date); + Session save(Session session); Optional findBySessionId(UUID sessionId); diff --git a/src/main/java/com/soupulsar/domain/repository/SpecialistProfileRepository.java b/src/main/java/com/soupulsar/domain/repository/SpecialistProfileRepository.java index 41b796a..cb86fd0 100644 --- a/src/main/java/com/soupulsar/domain/repository/SpecialistProfileRepository.java +++ b/src/main/java/com/soupulsar/domain/repository/SpecialistProfileRepository.java @@ -1,7 +1,11 @@ package com.soupulsar.domain.repository; +import com.soupulsar.domain.model.enums.SpecialistType; import com.soupulsar.domain.model.specialist.SpecialistProfile; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import java.math.BigDecimal; import java.util.Optional; import java.util.UUID; @@ -9,5 +13,5 @@ public interface SpecialistProfileRepository { SpecialistProfile save(SpecialistProfile profile); Optional findById(UUID id); - + Page findAll(SpecialistType type, BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable); } diff --git a/src/main/java/com/soupulsar/domain/repository/UserRepository.java b/src/main/java/com/soupulsar/domain/repository/UserRepository.java index 225f1ac..e1c12f5 100644 --- a/src/main/java/com/soupulsar/domain/repository/UserRepository.java +++ b/src/main/java/com/soupulsar/domain/repository/UserRepository.java @@ -1,7 +1,13 @@ package com.soupulsar.domain.repository; +import com.soupulsar.domain.model.enums.UserRole; +import com.soupulsar.domain.model.enums.UserStatus; import com.soupulsar.domain.model.user.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -13,4 +19,6 @@ public interface UserRepository { boolean existsByEmail(String email); boolean existsByCpf(String cpf); boolean existsById(UUID userId); + Page findAll(UserStatus status, UserRole role, Pageable pageable); + Map findAllByUserId(List specialistIds); } diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/entity/client/ClientProfileEntity.java b/src/main/java/com/soupulsar/infrastructure/persistence/entity/client/ClientProfileEntity.java index 236d15b..960d46c 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/entity/client/ClientProfileEntity.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/entity/client/ClientProfileEntity.java @@ -2,8 +2,6 @@ import jakarta.persistence.Embedded; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Getter; @@ -19,10 +17,6 @@ public class ClientProfileEntity { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private UUID profileId; private UUID userId; private Date dateOfBirth; diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/entity/specialist/SpecialistProfileEntity.java b/src/main/java/com/soupulsar/infrastructure/persistence/entity/specialist/SpecialistProfileEntity.java index c5364c5..0f16418 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/entity/specialist/SpecialistProfileEntity.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/entity/specialist/SpecialistProfileEntity.java @@ -1,18 +1,11 @@ package com.soupulsar.infrastructure.persistence.entity.specialist; -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.Table; +import com.soupulsar.domain.model.enums.SpecialistType; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -24,12 +17,13 @@ public class SpecialistProfileEntity { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private UUID profileId; private UUID userId; + private String registrationNumber; + private BigDecimal sessionPrice; + + @Enumerated(EnumType.STRING) + private SpecialistType specialistType; @Embedded private PresentationEmbeddable presentation; diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/entity/user/UserEntity.java b/src/main/java/com/soupulsar/infrastructure/persistence/entity/user/UserEntity.java index e6e0255..046dd0b 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/entity/user/UserEntity.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/entity/user/UserEntity.java @@ -7,8 +7,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Getter; @@ -23,9 +21,6 @@ public class UserEntity { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @Column(nullable = false, unique = true) private UUID userId; diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/mapper/client/ClientProfileMapper.java b/src/main/java/com/soupulsar/infrastructure/persistence/mapper/client/ClientProfileMapper.java index 5993dcf..ebf0b6c 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/mapper/client/ClientProfileMapper.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/mapper/client/ClientProfileMapper.java @@ -12,7 +12,6 @@ public static ClientProfileEntity toEntity(ClientProfile clientProfile) { if (clientProfile == null) return null; ClientProfileEntity entity = new ClientProfileEntity(); - entity.setProfileId(clientProfile.getProfileId()); entity.setUserId(clientProfile.getUserId()); entity.setDateOfBirth(clientProfile.getDateOfBirth()); entity.setEmergencyContact(EmergencyContactMapper.toEmbeddable(clientProfile.getEmergencyContact())); @@ -23,7 +22,6 @@ public static ClientProfile toModel(ClientProfileEntity entity) { if (entity == null) return null; return ClientProfile.builder() - .profileId(entity.getProfileId()) .userId(entity.getUserId()) .dateOfBirth(entity.getDateOfBirth()) .emergencyContact(EmergencyContactMapper.toValueObject(entity.getEmergencyContact())) diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/mapper/specialist/SpecialistProfileMapper.java b/src/main/java/com/soupulsar/infrastructure/persistence/mapper/specialist/SpecialistProfileMapper.java index 43ba4ef..73ea8ef 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/mapper/specialist/SpecialistProfileMapper.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/mapper/specialist/SpecialistProfileMapper.java @@ -12,12 +12,13 @@ public static SpecialistProfileEntity toEntity(SpecialistProfile specialistProfi if (specialistProfile == null) return null; SpecialistProfileEntity entity = new SpecialistProfileEntity(); - entity.setProfileId(specialistProfile.getProfileId()); entity.setUserId(specialistProfile.getUserId()); entity.setRegistrationNumber(specialistProfile.getRegistrationNumber()); entity.setApproaches(specialistProfile.getApproaches()); entity.setFormations(specialistProfile.getFormations()); entity.setSpecialties(specialistProfile.getSpecialties()); + entity.setSessionPrice(specialistProfile.getSessionPrice()); + entity.setSpecialistType(specialistProfile.getSpecialistType()); entity.setPresentation(PresentationMapper.toEmbeddable(specialistProfile.getPresentation())); return entity; @@ -28,12 +29,13 @@ public static SpecialistProfile toModel(SpecialistProfileEntity entity) { if (entity == null) return null; return SpecialistProfile.builder() - .profileId(entity.getProfileId()) .userId(entity.getUserId()) .registrationNumber(entity.getRegistrationNumber()) .formations(entity.getFormations()) .approaches(entity.getApproaches()) .specialties(entity.getSpecialties()) + .specialistType(entity.getSpecialistType()) + .sessionPrice(entity.getSessionPrice()) .presentation(PresentationMapper.toValueObject(entity.getPresentation())) .build(); } diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/ClientProfileJpaRepository.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/ClientProfileJpaRepository.java index 268850d..b6fa38b 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/ClientProfileJpaRepository.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/ClientProfileJpaRepository.java @@ -3,7 +3,11 @@ import com.soupulsar.infrastructure.persistence.entity.client.ClientProfileEntity; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; import java.util.UUID; -public interface ClientProfileJpaRepository extends JpaRepository { -} +public interface ClientProfileJpaRepository extends JpaRepository { + + Optional findByUserId(UUID userId); + +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/SessionJpaRepository.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/SessionJpaRepository.java index 6257edd..955f5a9 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/SessionJpaRepository.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/SessionJpaRepository.java @@ -3,7 +3,9 @@ import com.soupulsar.infrastructure.persistence.entity.session.SessionEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -17,7 +19,31 @@ public interface SessionJpaRepository extends JpaRepository AND s.startAt < :end AND s.endAt > :start """) - List findOverlappingSessions(UUID specialistId, LocalDateTime start, LocalDateTime end); + List findOverlappingSessions(@Param("specialistId") UUID specialistId, @Param("start") LocalDateTime start, @Param("end") LocalDateTime end); + + @Query(""" + SELECT s.specialistId, COUNT(s) + FROM SessionEntity s + WHERE s.status = 'COMPLETED' + AND s.specialistId IN :specialistIds + GROUP BY s.specialistId + """) + List countCompletedSessionsBySpecialistIds(@Param("specialistIds") List specialistIds); + + @Query(""" + SELECT COUNT(s) + FROM SessionEntity s + WHERE s.specialistId = :specialistId + AND s.status = 'COMPLETED' + """) + Long countCompletedSessionsBySpecialistId(@Param("specialistId") UUID specialistId); + + @Query(""" + SELECT s FROM SessionEntity s + WHERE s.specialistId = :specialistId + AND CAST(s.startAt AS date) = :date + """) + List findBySpecialistIdAndDate(@Param("specialistId") UUID specialistId, @Param("date") LocalDate date); Optional findBySessionId(UUID sessionId); } diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/SpecialistProfileJpaRepository.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/SpecialistProfileJpaRepository.java index 953c39e..b394e9c 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/SpecialistProfileJpaRepository.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/SpecialistProfileJpaRepository.java @@ -2,8 +2,13 @@ import com.soupulsar.infrastructure.persistence.entity.specialist.SpecialistProfileEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.Optional; import java.util.UUID; -public interface SpecialistProfileJpaRepository extends JpaRepository { -} +public interface SpecialistProfileJpaRepository extends JpaRepository, JpaSpecificationExecutor { + + Optional findByUserId(UUID userId); + +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/UserJpaRepository.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/UserJpaRepository.java index 2b19349..c2d951a 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/UserJpaRepository.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/UserJpaRepository.java @@ -2,14 +2,19 @@ import com.soupulsar.infrastructure.persistence.entity.user.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import java.util.List; import java.util.Optional; import java.util.UUID; -public interface UserJpaRepository extends JpaRepository { +public interface UserJpaRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByEmail(String email); boolean existsByEmail(String email); boolean existsByCpf(String cpf); + boolean existsByUserId(UUID userId); + Optional findByUserId(UUID userId); + List findAllByUserIdIn(List userIds); -} +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/ClientProfileRepositoryImpl.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/ClientProfileRepositoryImpl.java index ad62982..c3496ea 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/ClientProfileRepositoryImpl.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/ClientProfileRepositoryImpl.java @@ -27,6 +27,6 @@ public ClientProfile save(ClientProfile profile) { @Override public Optional findById(UUID id) { - return jpaRepository.findById(id).map(ClientProfileMapper::toModel); + return jpaRepository.findByUserId(id).map(ClientProfileMapper::toModel); } } \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SessionRepositoryImpl.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SessionRepositoryImpl.java index 49edf53..3b04ff7 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SessionRepositoryImpl.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SessionRepositoryImpl.java @@ -8,10 +8,12 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Arrays.stream; @RequiredArgsConstructor @Repository @@ -19,6 +21,35 @@ public class SessionRepositoryImpl implements SessionRepository { private final SessionJpaRepository jpaRepository; + @Override + public Map countCompletedSessionsBySpecialistIds(List specialistIds) { + if (specialistIds == null || specialistIds.isEmpty()) return Collections.emptyMap(); + + List results = jpaRepository.countCompletedSessionsBySpecialistIds(specialistIds); + + return results.stream().collect(Collectors.toMap( + row -> (UUID) row[0], + row -> (Long) row[1] + )); + } + + @Override + public Long countCompletedSessionsBySpecialistId(UUID specialistId) { + if (specialistId == null) return 0L; + + return jpaRepository.countCompletedSessionsBySpecialistId(specialistId); + } + + @Override + public List findBySpecialistIdAndDate(UUID specialistId, LocalDate date) { + if (specialistId == null) return Collections.emptyList(); + + return jpaRepository.findBySpecialistIdAndDate(specialistId, date) + .stream() + .map(SessionMapper::toModel) + .toList(); + } + @Override public Session save(Session session) { SessionEntity entity = SessionMapper.toEntity(session); diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SpecialistProfileRepositoryImpl.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SpecialistProfileRepositoryImpl.java index ffc8fc0..b1273f6 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SpecialistProfileRepositoryImpl.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/SpecialistProfileRepositoryImpl.java @@ -1,13 +1,19 @@ package com.soupulsar.infrastructure.persistence.repository.impl; +import com.soupulsar.domain.model.enums.SpecialistType; import com.soupulsar.domain.model.specialist.SpecialistProfile; import com.soupulsar.domain.repository.SpecialistProfileRepository; import com.soupulsar.infrastructure.persistence.entity.specialist.SpecialistProfileEntity; import com.soupulsar.infrastructure.persistence.mapper.specialist.SpecialistProfileMapper; import com.soupulsar.infrastructure.persistence.repository.SpecialistProfileJpaRepository; +import com.soupulsar.infrastructure.persistence.specification.SpecialistSpecifications; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Repository; +import java.math.BigDecimal; import java.util.Optional; import java.util.UUID; @@ -26,6 +32,19 @@ public SpecialistProfile save(SpecialistProfile profile) { @Override public Optional findById(UUID id) { - return Optional.empty(); + return jpaRepository.findByUserId(id).map(SpecialistProfileMapper::toModel); } -} + + @Override + public Page findAll(SpecialistType type, BigDecimal minPrice, BigDecimal maxPrice, Pageable pageable) { + Specification spec = Specification.unrestricted(); + + if (type != null) spec = spec.and(SpecialistSpecifications.hasType(type)); + + if (minPrice != null) spec = spec.and(SpecialistSpecifications.hasPriceRange(minPrice, null)); + + if (maxPrice != null) spec = spec.and(SpecialistSpecifications.hasPriceRange(null, maxPrice)); + + return jpaRepository.findAll(spec, pageable).map(SpecialistProfileMapper::toModel); + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/UserRepositoryImpl.java b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/UserRepositoryImpl.java index 501287a..299fec0 100644 --- a/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/com/soupulsar/infrastructure/persistence/repository/impl/UserRepositoryImpl.java @@ -1,15 +1,25 @@ package com.soupulsar.infrastructure.persistence.repository.impl; +import com.soupulsar.domain.model.enums.UserRole; +import com.soupulsar.domain.model.enums.UserStatus; import com.soupulsar.domain.model.user.User; import com.soupulsar.domain.repository.UserRepository; import com.soupulsar.infrastructure.persistence.entity.user.UserEntity; import com.soupulsar.infrastructure.persistence.mapper.UserMapper; import com.soupulsar.infrastructure.persistence.repository.UserJpaRepository; +import com.soupulsar.infrastructure.persistence.specification.UserSpecifications; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Repository; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; @RequiredArgsConstructor @Repository @@ -32,7 +42,7 @@ public Optional findByEmail(String email) { @Override public Optional findById(UUID userId) { - return jpaRepository.findById(userId).map(UserMapper::toModel); + return jpaRepository.findByUserId(userId).map(UserMapper::toModel); } @Override @@ -47,6 +57,28 @@ public boolean existsByCpf(String cpf) { @Override public boolean existsById(UUID userId) { - return jpaRepository.existsById(userId); + return jpaRepository.existsByUserId(userId); + } + + @Override + public Page findAll(UserStatus status, UserRole role, Pageable pageable) { + Specification spec = Specification.unrestricted(); + + if (status != null) { + spec = spec.and(UserSpecifications.hasStatus(status)); + } + + if (role != null) { + spec = spec.and(UserSpecifications.hasRole(role)); + } + return jpaRepository.findAll(spec, pageable).map(UserMapper::toModel); + } + + @Override + public Map findAllByUserId(List userIds) { + if (userIds == null || userIds.isEmpty()) return Collections.emptyMap(); + return jpaRepository.findAllByUserIdIn(userIds) + .stream() + .collect(Collectors.toMap(UserEntity::getUserId, UserMapper::toModel)); } } diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/specification/SpecialistSpecifications.java b/src/main/java/com/soupulsar/infrastructure/persistence/specification/SpecialistSpecifications.java new file mode 100644 index 0000000..c3dba89 --- /dev/null +++ b/src/main/java/com/soupulsar/infrastructure/persistence/specification/SpecialistSpecifications.java @@ -0,0 +1,27 @@ +package com.soupulsar.infrastructure.persistence.specification; + +import com.soupulsar.domain.model.enums.SpecialistType; +import com.soupulsar.infrastructure.persistence.entity.specialist.SpecialistProfileEntity; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.data.jpa.domain.Specification; + +import java.math.BigDecimal; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class SpecialistSpecifications { + + public static Specification hasType(SpecialistType specialistType) { + return (root, criteriaQuery, criteriaBuilder) -> + specialistType == null ? null : criteriaBuilder.equal(root.get("specialistType"), specialistType); + } + + public static Specification hasPriceRange(BigDecimal min, BigDecimal max) { + return (root, criteriaQuery, criteriaBuilder) -> { + if (min != null && max != null) return criteriaBuilder.between(root.get("sessionPrice"), min, max); + if (min != null) return criteriaBuilder.greaterThanOrEqualTo(root.get("sessionPrice"), min); + if (max != null) return criteriaBuilder.lessThanOrEqualTo(root.get("sessionPrice"), max); + return criteriaBuilder.conjunction(); + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/persistence/specification/UserSpecifications.java b/src/main/java/com/soupulsar/infrastructure/persistence/specification/UserSpecifications.java new file mode 100644 index 0000000..3b5449a --- /dev/null +++ b/src/main/java/com/soupulsar/infrastructure/persistence/specification/UserSpecifications.java @@ -0,0 +1,24 @@ +package com.soupulsar.infrastructure.persistence.specification; + + +import com.soupulsar.domain.model.enums.UserRole; +import com.soupulsar.domain.model.enums.UserStatus; +import com.soupulsar.infrastructure.persistence.entity.user.UserEntity; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.data.jpa.domain.Specification; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class UserSpecifications { + + public static Specification hasStatus(UserStatus status) { + return (root, criteriaQuery, criteriaBuilder) -> + status == null ? null : criteriaBuilder.equal(root.get("status"), status); + } + + public static Specification hasRole(UserRole role) { + return (root, criteriaQuery, criteriaBuilder) -> + role == null ? null : criteriaBuilder.equal(root.get("role"), role); + } + +} \ No newline at end of file diff --git a/src/main/java/com/soupulsar/infrastructure/security/CustomUserDetailsService.java b/src/main/java/com/soupulsar/infrastructure/security/CustomUserDetailsService.java index be06656..1d009ab 100644 --- a/src/main/java/com/soupulsar/infrastructure/security/CustomUserDetailsService.java +++ b/src/main/java/com/soupulsar/infrastructure/security/CustomUserDetailsService.java @@ -8,6 +8,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import java.util.UUID; + @Service @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { @@ -16,13 +18,13 @@ public class CustomUserDetailsService implements UserDetailsService { @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException { - var user = userRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); + var user = userRepository.findById(UUID.fromString(id)) + .orElseThrow(() -> new UsernameNotFoundException("User not found with id: " + id)); return User.builder() - .username(user.getEmail()) + .username(user.getUserId().toString()) .password(user.getPasswordHash()) .roles(user.getRole().name()) .build(); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e68634b..00991d3 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -5,4 +5,20 @@ spring: security: jwt: secret: ${JWT_SECRET:jwt-super-secret-key-place-holder} - expiration: ${JWT_EXPIRATION:3600000} \ No newline at end of file + expiration: ${JWT_EXPIRATION:3600000} + + datasource: + url: jdbc:h2:mem:soupulsar;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + driver-class-name: org.h2.Driver + username: sa + password: + + + h2: + console: + enabled: true + settings: + web-allow-others: true + + jpa: + show-sql: true \ No newline at end of file diff --git a/src/test/java/com/soupulsar/modulith/auth/application/usecase/AuthenticateUserUseCaseTest.java b/src/test/java/com/soupulsar/modulith/auth/application/usecase/AuthenticateUserUseCaseTest.java index 3f29d02..4f8fb40 100644 --- a/src/test/java/com/soupulsar/modulith/auth/application/usecase/AuthenticateUserUseCaseTest.java +++ b/src/test/java/com/soupulsar/modulith/auth/application/usecase/AuthenticateUserUseCaseTest.java @@ -5,6 +5,7 @@ import com.soupulsar.application.security.JwtService; import com.soupulsar.application.security.PasswordHasher; import com.soupulsar.application.usecase.AuthenticateUserUseCase; +import com.soupulsar.domain.exceptions.UserNotFoundException; import com.soupulsar.domain.model.user.User; import com.soupulsar.domain.model.enums.UserRole; import com.soupulsar.domain.model.enums.UserStatus; @@ -117,8 +118,8 @@ void shouldThrowWhenUserNotFound() { when(userRepository.findByEmail(request.email())).thenReturn(Optional.empty()); - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> useCase.execute(request)); - assertEquals("Invalid email or password", ex.getMessage()); + UserNotFoundException ex = assertThrows(UserNotFoundException.class, () -> useCase.execute(request)); + assertEquals(UserNotFoundException.class, ex.getClass()); verify(jwtService, never()).generateToken(any()); } } \ No newline at end of file