diff --git a/src/main/java/org/example/vet1177/controller/ActivityLogController.java b/src/main/java/org/example/vet1177/controller/ActivityLogController.java index de6e6b7f..7b1aca63 100644 --- a/src/main/java/org/example/vet1177/controller/ActivityLogController.java +++ b/src/main/java/org/example/vet1177/controller/ActivityLogController.java @@ -28,7 +28,7 @@ public List getLogsByRecord( @PathVariable UUID recordId, @RequestHeader("userId") UUID userId ) { - User user = userService.getById(userId); + User user = userService.getUserEntityById(userId); return activityLogService.getByRecord(recordId, user) .stream() diff --git a/src/main/java/org/example/vet1177/controller/MedicalRecordController.java b/src/main/java/org/example/vet1177/controller/MedicalRecordController.java index 51c9c079..00d54f24 100644 --- a/src/main/java/org/example/vet1177/controller/MedicalRecordController.java +++ b/src/main/java/org/example/vet1177/controller/MedicalRecordController.java @@ -184,7 +184,7 @@ public ResponseEntity assignVet( @AuthenticationPrincipal User currentUser) { MedicalRecord record = medicalRecordService.getById(id); - User vetToAssign = userService.getById(request.vetId()); + User vetToAssign = userService.getUserEntityById(request.vetId()); medicalRecordPolicy.canAssignVet(currentUser, record, vetToAssign); return ResponseEntity.ok( diff --git a/src/main/java/org/example/vet1177/controller/UserController.java b/src/main/java/org/example/vet1177/controller/UserController.java new file mode 100644 index 00000000..39cc09ef --- /dev/null +++ b/src/main/java/org/example/vet1177/controller/UserController.java @@ -0,0 +1,60 @@ +package org.example.vet1177.controller; + +import jakarta.validation.Valid; +import org.example.vet1177.dto.request.user.UserRequest; +import org.example.vet1177.dto.response.user.UserResponse; +import org.example.vet1177.services.UserService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.example.vet1177.dto.request.user.UserUpdateRequest; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + //GET /users- Hämta alla användare + @GetMapping + public ResponseEntity> getAllUsers() { + List users = userService.getAllUsers(); + return ResponseEntity.ok(users); + } + + // GET /users/{id}- Hämta 1 användare + @GetMapping("/{id}") + public ResponseEntity getUserById(@PathVariable UUID id) { + UserResponse user = userService.getById(id); + return ResponseEntity.ok(user); + } + + //POST /users - skapa ny användare + @PostMapping + public ResponseEntity createUser(@Valid @RequestBody UserRequest request) { + UserResponse user = userService.createUser(request); + return ResponseEntity.status(HttpStatus.CREATED).body(user); + } + + //PUT /users/{id} - uppdatera användare + @PutMapping("/{id}") + public ResponseEntity updateUser(@PathVariable UUID id, + @Valid @RequestBody UserUpdateRequest request) { + UserResponse user = userService.updateUser(id, request); + return ResponseEntity.ok(user); + } + + //DELETE /users/{id} - Tar bort användare + @DeleteMapping("/{id}") + public ResponseEntity deleteUser(@PathVariable UUID id) { + userService.deleteUser(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/org/example/vet1177/dto/request/user/UserRequest.java b/src/main/java/org/example/vet1177/dto/request/user/UserRequest.java new file mode 100644 index 00000000..ae1cf0fd --- /dev/null +++ b/src/main/java/org/example/vet1177/dto/request/user/UserRequest.java @@ -0,0 +1,72 @@ +package org.example.vet1177.dto.request.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.example.vet1177.entities.Role; + +import java.util.UUID; + +public class UserRequest { + + @NotBlank + @Size(min = 2, max = 100) + private String name; + + @NotBlank + @Email + private String email; + + @NotBlank + @Size(min = 8, max = 100) + private String password; + + @NotNull + private Role role; + + private UUID clinicId; + + public UserRequest(){ + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } + + public UUID getClinicId() { + return clinicId; + } + + public void setClinicId(UUID clinicId) { + this.clinicId = clinicId; + } +} diff --git a/src/main/java/org/example/vet1177/dto/request/user/UserUpdateRequest.java b/src/main/java/org/example/vet1177/dto/request/user/UserUpdateRequest.java new file mode 100644 index 00000000..dcc50dcb --- /dev/null +++ b/src/main/java/org/example/vet1177/dto/request/user/UserUpdateRequest.java @@ -0,0 +1,41 @@ +package org.example.vet1177.dto.request.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; + +import java.util.UUID; + +public class UserUpdateRequest { + + @Size(min = 2, max = 100) + private String name; + + @Email + private String email; + + private UUID clinicId; + + public UUID getClinicId() { + return clinicId; + } + + public void setClinicId(UUID clinicId) { + this.clinicId = clinicId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/vet1177/dto/response/user/UserResponse.java b/src/main/java/org/example/vet1177/dto/response/user/UserResponse.java new file mode 100644 index 00000000..fe6212a1 --- /dev/null +++ b/src/main/java/org/example/vet1177/dto/response/user/UserResponse.java @@ -0,0 +1,86 @@ +package org.example.vet1177.dto.response.user; + +import org.example.vet1177.entities.Role; + +import java.time.Instant; +import java.util.UUID; + +public class UserResponse { + + private UUID id; + private String name; + private String email; + private Role role; + private UUID clinicId; + private Instant createdAt; + private Instant updatedAt; + + public UserResponse() { + } + + public UserResponse(UUID id, String name, String email, Role role, UUID clinicId, Instant createdAt, Instant updatedAt) { + this.id =id; + this.name =name; + this.email = email; + this.role = role; + this.clinicId = clinicId; + this.createdAt = createdAt; + this.updatedAt =updatedAt; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } + + public UUID getClinicId() { + return clinicId; + } + + public void setClinicId(UUID clinicId) { + this.clinicId = clinicId; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/org/example/vet1177/repository/MedicalRecordRepository.java b/src/main/java/org/example/vet1177/repository/MedicalRecordRepository.java index 9635d336..db8f424f 100644 --- a/src/main/java/org/example/vet1177/repository/MedicalRecordRepository.java +++ b/src/main/java/org/example/vet1177/repository/MedicalRecordRepository.java @@ -33,4 +33,8 @@ public interface MedicalRecordRepository extends JpaRepository findByAssignedVetId(UUID vetId); List findByStatus(RecordStatus status); boolean existsByPetIdAndClinicId(UUID petId, UUID clinicId); + boolean existsByOwnerId(UUID ownerId); + boolean existsByAssignedVetId(UUID vetId); + boolean existsByCreatedById(UUID userId); + boolean existsByUpdatedById(UUID userId); } \ No newline at end of file diff --git a/src/main/java/org/example/vet1177/repository/PetRepository.java b/src/main/java/org/example/vet1177/repository/PetRepository.java index 2be1c431..dd221448 100644 --- a/src/main/java/org/example/vet1177/repository/PetRepository.java +++ b/src/main/java/org/example/vet1177/repository/PetRepository.java @@ -15,4 +15,6 @@ public interface PetRepository extends JpaRepository { //hämta ett specifikt pet för en specifik owner Optional findByIdAndOwnerId(UUID petId, UUID ownerId); + + boolean existsByOwner_Id(UUID ownerId); } \ No newline at end of file diff --git a/src/main/java/org/example/vet1177/repository/UserRepository.java b/src/main/java/org/example/vet1177/repository/UserRepository.java index 9651ae7d..49058751 100644 --- a/src/main/java/org/example/vet1177/repository/UserRepository.java +++ b/src/main/java/org/example/vet1177/repository/UserRepository.java @@ -9,4 +9,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); + boolean existsByEmail(String email); + boolean existsByEmailAndIdNot(String email, UUID id); } diff --git a/src/main/java/org/example/vet1177/services/UserService.java b/src/main/java/org/example/vet1177/services/UserService.java index 17d8be0b..23edc553 100644 --- a/src/main/java/org/example/vet1177/services/UserService.java +++ b/src/main/java/org/example/vet1177/services/UserService.java @@ -1,36 +1,194 @@ package org.example.vet1177.services; +import org.example.vet1177.dto.request.user.UserRequest; +import org.example.vet1177.dto.request.user.UserUpdateRequest; +import org.example.vet1177.dto.response.user.UserResponse; +import org.example.vet1177.entities.Clinic; import org.example.vet1177.entities.Role; import org.example.vet1177.entities.User; +import org.example.vet1177.exception.BusinessRuleException; import org.example.vet1177.exception.ResourceNotFoundException; +import org.example.vet1177.repository.ClinicRepository; +import org.example.vet1177.repository.MedicalRecordRepository; +import org.example.vet1177.repository.PetRepository; import org.example.vet1177.repository.UserRepository; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; + +import java.util.List; import java.util.UUID; @Service public class UserService { - + // TODO: Implementera Spring Security för autentisering och auktorisering + private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); private final UserRepository userRepository; + private final ClinicRepository clinicRepository; + private final PetRepository petRepository; + private final MedicalRecordRepository medicalRecordRepository; - public UserService(UserRepository userRepository){ + public UserService(UserRepository userRepository, + ClinicRepository clinicRepository, + PetRepository petRepository, + MedicalRecordRepository medicalRecordRepository) { this.userRepository = userRepository; + this.clinicRepository = clinicRepository; + this.petRepository = petRepository; + this.medicalRecordRepository = medicalRecordRepository; } - public User createUser(String name, String email, String passwordHash, Role role){ - User user = new User(name, email, passwordHash, role); - return userRepository.save(user); + public UserResponse createUser(UserRequest request) { + validateEmailUnique(request.getEmail()); + String passwordHash = passwordEncoder.encode(request.getPassword()); + User user = new User( + request.getName(), + request.getEmail(), + passwordHash, + request.getRole()); + applyClinicRules(user, request.getClinicId()); + try { + return mapToResponse(userRepository.save(user)); + } catch (DataIntegrityViolationException e) { + throw new BusinessRuleException("Email används redan"); + } + } + + // TODO: GET /users/search?email= - Sök användare på email, kräver ADMIN-roll (implementera när Spring Security är på plats) + public User getByEmail(String email){ + return userRepository.findByEmail(email) + .orElseThrow(() -> new ResourceNotFoundException("User", email)); } - public User getById(UUID id){ + // Returnerar User-entiteten, används internt när andra services behöver ett User-objekt. OK? - annars + public User getUserEntityById(UUID id) { return userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User", id)); } - public User getByEmail(String email){ - return userRepository.findByEmail(email) - .orElseThrow(() -> new ResourceNotFoundException("User", email)); + // Returnerar UserResponse DTO, används av UserController för att exponera användardata till klienten. + // Ändra anrop från getById() till getUserEntityById() i ActivityLogController + public UserResponse getById(UUID id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("User", id)); + return mapToResponse(user); + } + + //Get all users + public List getAllUsers() { + return userRepository.findAll() + .stream() + .map(this::mapToResponse) + .toList(); + } + + //Update user + public UserResponse updateUser(UUID id, UserUpdateRequest request) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("User", id)); + + if (request.getEmail() != null) { + validateEmailUniqueForUpdate(request.getEmail(), id); + user.setEmail(request.getEmail()); + } + + if (request.getName() != null) { + user.setName(request.getName()); + } + + if (request.getClinicId() != null) { + applyClinicRulesForUpdate(user, request.getClinicId()); + } + try { + return mapToResponse(userRepository.save(user)); + } catch (DataIntegrityViolationException e) { + throw new BusinessRuleException("Email används redan"); + } } + //Delete user + public void deleteUser(UUID id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("User", id)); + + if (petRepository.existsByOwner_Id(id)) { + throw new BusinessRuleException("Användaren har kopplade djur och kan inte raderas"); + } + if (medicalRecordRepository.existsByOwnerId(id)) { + throw new BusinessRuleException("Användaren är ägare på journalposter och kan inte raderas"); + } + if (medicalRecordRepository.existsByAssignedVetId(id)) { + throw new BusinessRuleException("Användaren är tilldelad veterinär på journalposter och kan inte raderas"); + } + if (medicalRecordRepository.existsByCreatedById(id)) { + throw new BusinessRuleException("Användaren har skapat journalposter och kan inte raderas"); + } + if (medicalRecordRepository.existsByUpdatedById(id)) { + throw new BusinessRuleException("Användaren har uppdaterat journalposter och kan inte raderas"); + } + + try { + userRepository.delete(user); + } catch (DataIntegrityViolationException e) { + throw new BusinessRuleException("Användaren kan inte raderas på grund av kopplade poster"); + } + } + + //Helper + private UserResponse mapToResponse(User user) { + return new UserResponse( + user.getId(), + user.getName(), + user.getEmail(), + user.getRole(), + user.getClinic() != null ? user.getClinic().getId() : null, + user.getCreatedAt(), + user.getUpdatedAt() + ); + } + + private void validateEmailUnique(String email) { + if (userRepository.existsByEmail(email)) { + throw new BusinessRuleException("Email används redan"); + } + } + + private void validateEmailUniqueForUpdate(String email, UUID userId) { + if (userRepository.existsByEmailAndIdNot(email, userId)) { + throw new BusinessRuleException("Email används redan"); + } + } + + // Vid skapande — VET måste ha klinik, övriga får inte ha det + private void applyClinicRules(User user, UUID clinicId) { + if (user.getRole() == Role.VET) { + if (clinicId == null) { + throw new BusinessRuleException("Veterinär måste vara kopplad till en klinik"); + } + Clinic clinic = clinicRepository.findById(clinicId) + .orElseThrow(() -> new ResourceNotFoundException("Clinic", clinicId)); + user.setClinic(clinic); + } else { + if (clinicId != null) { + throw new BusinessRuleException("Endast veterinärer kan kopplas till en klinik"); + } + user.setClinic(null); + } + } + + // Vid uppdatering — klinik ändras bara om clinicId skickas med + private void applyClinicRulesForUpdate(User user, UUID clinicId) { + if (user.getRole() != Role.VET) { + throw new BusinessRuleException("Endast veterinärer kan kopplas till en klinik"); + } + Clinic clinic = clinicRepository.findById(clinicId) + .orElseThrow(() -> new ResourceNotFoundException("Clinic", clinicId)); + user.setClinic(clinic); + } + + + }