Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrum 95 #107

Merged
merged 26 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a771a50
feat: create PetController to get pet list
Hsbalazs Sep 1, 2024
d2e69be
test: create PetControllerTest with mockMvc
Hsbalazs Sep 2, 2024
4472874
fix: fix pets arguments
Hsbalazs Sep 5, 2024
045d984
feature: PetList has been created and made some reviewdog repairings
Hsbalazs Sep 7, 2024
50033a9
chore: some reviewdog repairings
Hsbalazs Sep 7, 2024
c73d38f
feature: create petList button under main page
Hsbalazs Sep 7, 2024
791af79
feature: add catch error for the PetList
Hsbalazs Sep 8, 2024
da21c15
feature: add petlist to the router in App.tsx
Hsbalazs Sep 9, 2024
22800f8
chore: format the table of pets
Hsbalazs Sep 9, 2024
5be7816
test: test PetList
Hsbalazs Sep 9, 2024
88633e7
test: add PetServiceImpl test
Hsbalazs Sep 9, 2024
d4383a4
test: add test points to ProfileUpdate
Hsbalazs Sep 9, 2024
f0b3bf8
fix: linter fix
Hsbalazs Sep 9, 2024
e94eb2a
chore: add "/pets" endpoint to the securityFilterChain
Hsbalazs Sep 10, 2024
159408d
chore: validate birthDate, lastCheckUp and nextCheckUp of Pet class
Hsbalazs Sep 10, 2024
fe91eff
chore: delete not used import in OwnerServiceImpl
Hsbalazs Sep 10, 2024
485c965
chore: add loadUserByUserName func to OwnerService.java
Hsbalazs Sep 10, 2024
5c17bb4
Merge branch 'main' into SCRUM-95
Hsbalazs Sep 10, 2024
57b37e6
fix: repair reviewdog finding
Hsbalazs Sep 10, 2024
ae4d1b0
Merge branch 'SCRUM-95' of https://github.com/gfa-cc-after/Pet-Clinic…
Hsbalazs Sep 10, 2024
d8287e8
fix: repair reviewdog finding
Hsbalazs Sep 10, 2024
b5ad13a
chore: delete .tsx extension from the import lines in App.tsx
Hsbalazs Sep 18, 2024
2af6440
chore: delete lastCheckUp and nextCheckUp date from httpClient.ts Pet…
Hsbalazs Sep 18, 2024
42a8926
chore: wrap PetList into ProtectedPages in App.tsx
Hsbalazs Sep 18, 2024
d3c7247
chore: linter repairing
Hsbalazs Sep 18, 2024
929e83a
Merge branch 'main' into SCRUM-95
Hsbalazs Sep 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(allowedUrls).permitAll()
.requestMatchers("/profile-update").authenticated()
.requestMatchers("/pets").authenticated()
.anyRequest().authenticated()
)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.greenfoxacademy.backend.controller;

import com.greenfoxacademy.backend.dtos.PetListResponseDto;
import com.greenfoxacademy.backend.services.pet.PetService;
import java.security.Principal;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* A REST controller that handles operations related to users' pets.
*
* @author Your Name
*/
@RequiredArgsConstructor
@RestController
public class PetController {
private final PetService petService;

@GetMapping("/pets")
public ResponseEntity<PetListResponseDto> getPets(Principal owner) {
return ResponseEntity.status(HttpStatus.OK).body(petService.getOwnerPets(owner.getName()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.greenfoxacademy.backend.dtos;

import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.PastOrPresent;
import java.util.Date;
import lombok.Data;

/**
* A data transfer object for pet details.
*
* @author Your Name
*/
@Data
public class PetDetailsDto {
Hsbalazs marked this conversation as resolved.
Show resolved Hide resolved
@NotBlank
String name;
@NotBlank
String breed;
@NotBlank
String sex;
@PastOrPresent(message = "The birth date must be in the past or present")
Date birthDate;
@PastOrPresent(message = "The last check-up date must be in the past or present")
Date lastCheckUp;
ramesz6 marked this conversation as resolved.
Show resolved Hide resolved
@FutureOrPresent(message = "The next check-up date must be in the future or present")
Date nextCheckUp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.greenfoxacademy.backend.dtos;

import java.util.List;

/**
* A data transfer object for a list of pets.
*
*/
public record PetListResponseDto(
List<PetDetailsDto> pets
){
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
@AllArgsConstructor
@Table(name = "_owner")
public class Owner extends User {
@OneToMany(mappedBy = "petOwner")
@OneToMany(mappedBy = "owner")
private List<Pet> pets;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ public class Pet {
private Integer id;

@Column(nullable = false)
private String petName;
private String petBreed;
private String petSex;
private Date petBirthDate;
private String name;
private String breed;
private String sex;
private Date birthDate;
private Date lastCheckUp;
private Date nextCheckUp;

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "petOwner_Id")
private Owner petOwner;
@JoinColumn(name = "owner_id")
private Owner owner;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.security.core.userdetails.UserDetails;
Expand All @@ -22,6 +23,7 @@
@NoArgsConstructor
public abstract class User implements UserDetails {

@Getter
Hsbalazs marked this conversation as resolved.
Show resolved Hide resolved
@Id
@GeneratedValue
private Integer id;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.greenfoxacademy.backend.repositories;

import com.greenfoxacademy.backend.models.Pet;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

/**
* Repository to manage Pet entities.
*/
public interface PetRepository extends JpaRepository<Pet, Integer> {
List<Pet> findAllByOwnerId(Integer ownerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import jakarta.mail.internet.MimeMessage;
import java.util.UUID;
import lombok.RequiredArgsConstructor;

import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.greenfoxacademy.backend.services.pet;

import com.greenfoxacademy.backend.dtos.PetListResponseDto;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

/**
* Retrieves the pets of the specified owner.
*
* @param name The name of the owner.
* @return A response containing the owner's pets.
*/

public interface PetService {
PetListResponseDto getOwnerPets(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.greenfoxacademy.backend.services.pet;

import com.greenfoxacademy.backend.dtos.PetDetailsDto;
import com.greenfoxacademy.backend.dtos.PetListResponseDto;
import com.greenfoxacademy.backend.models.Pet;
import com.greenfoxacademy.backend.repositories.PetRepository;
import com.greenfoxacademy.backend.services.user.owner.OwnerService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
* Retrieves a list of pets owned by the user with the specified email.
*/
@Service
@RequiredArgsConstructor
public class PetServiceImpl implements PetService {
private final PetRepository petRepository;
private final OwnerService ownerService;
private final ModelMapper modelMapper = new ModelMapper();

/**
* Retrieves a list of pets owned by the user with the specified email.
*
* @param email the email of the pet owner
* @return a {@link PetListResponseDto} containing the list of pets
* @throws UsernameNotFoundException if the user with the specified email is not found
*/
@Override
public PetListResponseDto getOwnerPets(String email) {
List<Pet> petList = petRepository
.findAllByOwnerId(ownerService.findByEmail(email).getId());

List<PetDetailsDto> petDtoList = petList.stream()
.map(pet -> modelMapper.map(pet, PetDetailsDto.class))
.collect(Collectors.toList());

return new PetListResponseDto(petDtoList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@
import com.greenfoxacademy.backend.errors.UserAlreadyExistsError;
import com.greenfoxacademy.backend.models.Owner;
import java.util.UUID;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
* Service to manage {@link Owner} related actions.
*/
@Service
public interface OwnerService extends UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Owner findByEmail(String username);

RegisterResponseDto register(RegisterRequestDto userDto) throws UserAlreadyExistsError;

LoginResponseDto login(LoginRequestDto loginRequestDto) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@
import com.greenfoxacademy.backend.repositories.OwnerRepository;
import com.greenfoxacademy.backend.services.auth.AuthService;
import com.greenfoxacademy.backend.services.mail.EmailService;
import com.greenfoxacademy.backend.services.user.owner.OwnerService;
import jakarta.transaction.Transactional;

import java.util.UUID;

import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
Expand Down Expand Up @@ -104,7 +101,7 @@ public ProfileUpdateResponseDto profileUpdate(

@Cacheable(value = "profile-cache", key = "#username")
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
public Owner findByEmail(String username) throws UsernameNotFoundException {
Hsbalazs marked this conversation as resolved.
Show resolved Hide resolved
return ownerRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("No such user!"));
}
Expand All @@ -130,4 +127,22 @@ public void verifyUser(UUID id) {
userWithId.setVerificationId(null);
ownerRepository.save(userWithId);
}

/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the <code>UserDetails</code>
* object that comes back may have a username that is of a different case than what
* was actually requested..
*
* @param username the username identifying the user whose data is required.
* @return a fully populated user record (never <code>null</code>)
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return ownerRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("No such user!"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.mockito.Mockito.when;

import com.greenfoxacademy.backend.config.EmailConfiguration;
import com.greenfoxacademy.backend.services.pet.PetService;
import com.greenfoxacademy.backend.services.user.owner.OwnerService;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
Expand Down Expand Up @@ -36,6 +37,10 @@ class EmailServiceImplTest {
@MockBean
private OwnerService ownerService;

@MockBean
private PetService petService;
Hsbalazs marked this conversation as resolved.
Show resolved Hide resolved


private EmailServiceImpl emailService;

@BeforeEach
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.greenfoxacademy.backend.services.pet;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.greenfoxacademy.backend.models.Owner;
import com.greenfoxacademy.backend.models.Pet;
import com.greenfoxacademy.backend.repositories.OwnerRepository;
import com.greenfoxacademy.backend.repositories.PetRepository;
import jakarta.transaction.Transactional;
import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

/**
* Integration test class for PetServiceImpl.
* This class uses the following annotations:
* - {@link SpringBootTest}: Indicates that the class is a Spring Boot test that
* will start the full application context.
* - {@link AutoConfigureMockMvc}: Automatically configures MockMvc for testing web layer.
* - {@link Transactional}: Ensures that each test method runs within a transaction
* that is rolled back after the test completes.
*/
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class PetServiceImplTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private PetRepository petRepository;

@Autowired
private OwnerRepository ownerRepository;

/**
* Sets up mock data before each test.
* This method is executed before each test method in the current test class.
* It initializes mock data for owners and pets and saves them to the repository.
*/
@BeforeEach
public void setUp() {
// Set up mock data
Owner userWithPets = new Owner();
userWithPets.setEmail("userWithPets@example.com");
userWithPets.setPassword("Password");
Owner userWithNoPets = new Owner();
userWithNoPets.setEmail("userWithNoPets@example.com");
userWithNoPets.setPassword("Password");

ownerRepository.saveAll(Arrays.asList(userWithPets, userWithNoPets));

Pet pet1 = new Pet();
pet1.setName("Morzsi");
pet1.setOwner(userWithPets);
Pet pet2 = new Pet();
pet2.setName("Rusty");
pet2.setOwner(userWithPets);

petRepository.saveAll(Arrays.asList(pet1, pet2));
}

@Test
@WithMockUser(username = "userWithPets@example.com")
public void testCorrectEmailWithExistingPets() throws Exception {
mockMvc.perform(get("/pets")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.pets[0].name").value("Morzsi"))
.andExpect(jsonPath("$.pets[1].name").value("Rusty"));
}

@Test
@WithMockUser(username = "userWithNoPets@example.com")
public void testCorrectEmailWithNoExistingPets() throws Exception {
mockMvc.perform(get("/pets")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.pets").isEmpty());
}

@Test
@WithMockUser(username = "nonExistingUser@example.com")
public void testIncorrectEmail() throws Exception {
mockMvc.perform(get("/pets")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is4xxClientError());
}
}
Loading
Loading