From 95101645e7ef0fe108753d6c23a7f8325913fbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <xpokorn8@fi.muni.cz> Date: Wed, 5 Apr 2023 21:18:55 +0200 Subject: [PATCH 1/4] Course CRUD implementation, extensive change of id from String to Long --- .../model/dto/certificate/CertificateDto.java | 2 +- .../model/dto/common/DomainObjectDto.java | 2 +- .../model/dto/course/CourseCreateDto.java | 4 +- .../fuseri/model/dto/course/CourseDto.java | 16 +-- .../fuseri/model/dto/lecture/LectureDto.java | 2 +- .../org/fuseri/model/dto/user/UserDto.java | 5 +- application/module-language-school/pom.xml | 4 + .../common/DomainObject.java | 7 +- .../common/DomainService.java | 2 +- .../common/ResourceNotFoundException.java | 22 ++++ .../modulelanguageschool/course/Course.java | 5 +- .../course/CourseController.java | 37 +++--- .../course/CourseFacade.java | 49 ++++++++ .../course/CourseMapper.java | 27 +++++ .../course/CourseRepository.java | 9 ++ .../course/CourseService.java | 57 ++++++++++ .../user/UserController.java | 18 +-- .../user/UserRepository.java | 2 +- .../user/UserService.java | 2 +- .../src/main/resources/application.properties | 14 ++- .../course/CourseTest.java | 105 +++++++++--------- .../user/UserControllerTest.java | 14 +-- .../resources/application-test.properties | 14 +++ .../src/test/resources/logback.xml | 16 +++ 24 files changed, 330 insertions(+), 105 deletions(-) create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/ResourceNotFoundException.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java create mode 100644 application/module-language-school/src/test/resources/application-test.properties create mode 100644 application/module-language-school/src/test/resources/logback.xml diff --git a/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateDto.java b/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateDto.java index 52597af3..6d55d577 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/certificate/CertificateDto.java @@ -33,6 +33,6 @@ public class CertificateDto extends DomainObjectDto { private CertificateFileDto certificateFile; public CertificateDto() { - setId("0"); + setId(0L); } } diff --git a/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java b/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java index 2c1aff83..36ec98b0 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java @@ -11,5 +11,5 @@ public abstract class DomainObjectDto { @NotBlank @NotNull - private String id; + private Long id; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java index 88995655..64c78eec 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/course/CourseCreateDto.java @@ -28,10 +28,10 @@ public class CourseCreateDto { @NotNull(message = "Language type is required") @Valid - private LanguageTypeDto languageTypeDto; + private LanguageTypeDto language; @NotNull(message = "Proficiency level is required") @Valid - private ProficiencyLevelDto proficiencyLevelDto; + private ProficiencyLevelDto proficiency; } diff --git a/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java b/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java index 544d06f5..18e5695b 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/course/CourseDto.java @@ -20,7 +20,7 @@ import java.util.List; */ @Getter @Setter -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) public class CourseDto extends DomainObjectDto { @NotBlank(message = "Course name is required") @@ -33,22 +33,22 @@ public class CourseDto extends DomainObjectDto { @NotNull(message = "Language type is required") @Valid - private LanguageTypeDto languageTypeDto; + private LanguageTypeDto language; @NotNull(message = "Proficiency level is required") @Valid - private ProficiencyLevelDto proficiencyLevelDto; + private ProficiencyLevelDto proficiency; @NotNull(message = "Student's list is required") @Valid - private List<String> studentIds; + private List<Long> studentIds; - public CourseDto(String name, Integer capacity, LanguageTypeDto languageTypeDto, ProficiencyLevelDto proficiencyLevelDto) { - setId("0"); + public CourseDto(Long id, String name, Integer capacity, LanguageTypeDto languageTypeDto, ProficiencyLevelDto proficiencyLevelDto) { + this.setId(id); this.name = name; this.capacity = capacity; - this.languageTypeDto = languageTypeDto; - this.proficiencyLevelDto = proficiencyLevelDto; + this.language = languageTypeDto; + this.proficiency = proficiencyLevelDto; this.studentIds = new ArrayList<>(); } } diff --git a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java index df9b17a7..f54ac383 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java @@ -36,7 +36,7 @@ public class LectureDto extends DomainObjectDto { private String courseId; @NotNull(message = "Student IDs list cannot be null") - private List<String> studentIds; + private List<Long> studentIds; public LectureDto(LocalDateTime from, LocalDateTime to, String topic, Integer capacity, String lecturerId, String courseId) { this.from = from; diff --git a/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java b/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java index fce12aab..4d6eba73 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/user/UserDto.java @@ -1,10 +1,9 @@ package org.fuseri.model.dto.user; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import org.fuseri.model.dto.common.DomainObjectDto; import lombok.Getter; import lombok.Setter; +import org.fuseri.model.dto.common.DomainObjectDto; @Getter @Setter @@ -21,7 +20,7 @@ public class UserDto extends DomainObjectDto { public UserDto(String username, String email, String firstName, String lastName, AddressDto address) { - setId("0"); + setId(0L); this.username = username; this.email = email; this.firstName = firstName; diff --git a/application/module-language-school/pom.xml b/application/module-language-school/pom.xml index 42e3a5de..76585929 100644 --- a/application/module-language-school/pom.xml +++ b/application/module-language-school/pom.xml @@ -59,6 +59,10 @@ <version>0.0.1-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-tx</artifactId> + </dependency> </dependencies> diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainObject.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainObject.java index 8520e8aa..078871d9 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainObject.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainObject.java @@ -1,18 +1,19 @@ package org.fuseri.modulelanguageschool.common; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; import lombok.Getter; import lombok.Setter; -import java.util.UUID; - @Getter @Setter @MappedSuperclass public abstract class DomainObject { @Id - private String id = UUID.randomUUID().toString(); + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainService.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainService.java index 706071fb..426ef5c3 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainService.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/DomainService.java @@ -10,7 +10,7 @@ public abstract class DomainService<T extends DomainObject> { public static final int DEFAULT_PAGE_SIZE = 10; - public abstract JpaRepository<T, String> getRepository(); + public abstract JpaRepository<T, Long> getRepository(); @Transactional public T create(T entity) { diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/ResourceNotFoundException.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/ResourceNotFoundException.java new file mode 100644 index 00000000..2227c564 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/common/ResourceNotFoundException.java @@ -0,0 +1,22 @@ +package org.fuseri.modulelanguageschool.common; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException() { + } + + public ResourceNotFoundException(String message) { + super(message); + } + + public ResourceNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public ResourceNotFoundException(Throwable cause) { + super(cause); + } + + public ResourceNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java index 38f42dfa..455637c7 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java @@ -3,11 +3,14 @@ package org.fuseri.modulelanguageschool.course; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; import lombok.*; import org.fuseri.modulelanguageschool.common.DomainObject; @Getter @Setter +@Entity +@Table(name = "course") @NoArgsConstructor @AllArgsConstructor public class Course extends DomainObject { @@ -19,5 +22,5 @@ public class Course extends DomainObject { private Language language; @Enumerated(EnumType.STRING) - private ProficiencyLevel proficiencyLevel; + private ProficiencyLevel proficiency; } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java index 9646fb8f..75e92aa7 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java @@ -1,12 +1,15 @@ package org.fuseri.modulelanguageschool.course; import jakarta.validation.Valid; -import jakarta.validation.constraints.PositiveOrZero; import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; import org.fuseri.model.dto.course.LanguageTypeDto; import org.fuseri.model.dto.course.ProficiencyLevelDto; import org.fuseri.model.dto.user.UserDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; @@ -21,6 +24,12 @@ import java.util.List; public class CourseController { public static final String COURSE_NAME = "english b2 course"; + private final CourseFacade courseFacade; + + @Autowired + public CourseController(CourseFacade courseFacade) { + this.courseFacade = courseFacade; + } /** * Creates a new course. @@ -30,7 +39,7 @@ public class CourseController { */ @PostMapping("/create") public CourseDto create(@Valid @RequestBody CourseCreateDto dto) { - return new CourseDto(dto.getName(), dto.getCapacity(), dto.getLanguageTypeDto(), dto.getProficiencyLevelDto()); + return courseFacade.create(dto); } /** @@ -40,8 +49,8 @@ public class CourseController { * @return the CourseDto for the specified ID */ @GetMapping("/find/{id}") - public CourseDto find(@PathVariable String id) { - return new CourseDto(COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + public CourseDto find(@PathVariable Long id) { + return courseFacade.findById(id); } /** @@ -51,8 +60,8 @@ public class CourseController { * @return the Result containing the requested page of CourseDtos */ @GetMapping("/findAll") - public List<CourseDto> findAll(@RequestParam int page) { - return new ArrayList<>(); + public Page<CourseDto> findAll(@RequestParam int page) { + return courseFacade.findAll(PageRequest.of(page, 10, Sort.by(Sort.Direction.ASC, "name"))); } /** @@ -90,18 +99,18 @@ public class CourseController { * @return the updated CourseDto */ @PutMapping("/update/{id}") - public CourseDto update(@PathVariable String id, @Valid @RequestBody CourseCreateDto dto) { - return new CourseDto(dto.getName(), dto.getCapacity(), dto.getLanguageTypeDto(), dto.getProficiencyLevelDto()); + public CourseDto update(@PathVariable Long id, @Valid @RequestBody CourseCreateDto dto) { + return courseFacade.update(id, dto); } /** * Deletes a course by ID. * * @param id the ID of the course to delete - * @return true if the course was successfully deleted, false otherwise */ @DeleteMapping("/delete/{id}") - public void delete(@PathVariable String id) { + public void delete(@PathVariable Long id) { + courseFacade.delete(id); } @@ -113,8 +122,8 @@ public class CourseController { * @return the CourseDto representing the updated course */ @PatchMapping("/enrol/{id}") - public CourseDto enrol(@PathVariable String id, @RequestBody UserDto student) { - var course = new CourseDto(COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + public CourseDto enrol(@PathVariable Long id, @RequestBody UserDto student) { + var course = new CourseDto(0L, COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); course.setStudentIds(new ArrayList<>(List.of(student.getId()))); return course; } @@ -127,8 +136,8 @@ public class CourseController { * @return the CourseDto representing the updated course */ @PatchMapping("/expel/{id}") - public CourseDto expel(@PathVariable String id, @RequestBody UserDto student) { - return new CourseDto(COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + public CourseDto expel(@PathVariable Long id, @RequestBody UserDto student) { + return new CourseDto(0L, COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java new file mode 100644 index 00000000..9899db25 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java @@ -0,0 +1,49 @@ +package org.fuseri.modulelanguageschool.course; + +import org.fuseri.model.dto.course.CourseCreateDto; +import org.fuseri.model.dto.course.CourseDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class CourseFacade { + private final CourseService courseService; + private final CourseMapper courseMapper; + + @Autowired + public CourseFacade(CourseService courseService, CourseMapper courseMapper) { + this.courseService = courseService; + this.courseMapper = courseMapper; + } + + @Transactional + public CourseDto create(CourseCreateDto dto) { + return courseMapper.mapToDto(courseService.save(courseMapper.mapToCourse(dto))); + } + + @Cacheable(cacheNames = "courses", key = "#id") + @Transactional(readOnly = true) + public CourseDto findById(Long id) { + return courseMapper.mapToDto(courseService.findById(id)); + } + + @Transactional(readOnly = true) + public Page<CourseDto> findAll(Pageable pageable) { + return courseMapper.mapToPageDto(courseService.findAll(pageable)); + } + + @Transactional + public CourseDto update(Long id, CourseCreateDto dto) { + return courseMapper.mapToDto(courseService.update(id, courseMapper.mapToCourse(dto))); + } + + @Transactional + public void delete(Long id) { + courseService.delete(id); + } +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java new file mode 100644 index 00000000..9a5bf7c0 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseMapper.java @@ -0,0 +1,27 @@ +package org.fuseri.modulelanguageschool.course; + +import org.fuseri.model.dto.course.CourseCreateDto; +import org.fuseri.model.dto.course.CourseDto; +import org.mapstruct.Mapper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface CourseMapper { + + CourseDto mapToDto(Course course); + + Course mapToCourse(CourseDto courseDto); + + List<CourseDto> mapToList(List<Course> persons); + + + default Page<CourseDto> mapToPageDto(Page<Course> courses) { + return new PageImpl<>(mapToList(courses.getContent()), courses.getPageable(), courses.getTotalPages()); + } + + Course mapToCourse(CourseCreateDto dto); +} + diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java new file mode 100644 index 00000000..1dcad446 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java @@ -0,0 +1,9 @@ +package org.fuseri.modulelanguageschool.course; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CourseRepository extends JpaRepository<Course, Long> { + +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java new file mode 100644 index 00000000..eb206585 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java @@ -0,0 +1,57 @@ +package org.fuseri.modulelanguageschool.course; + +import org.fuseri.modulelanguageschool.common.ResourceNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +public class CourseService { + + private final CourseRepository courseRepository; + + @Autowired + public CourseService(CourseRepository courseRepository) { + this.courseRepository = courseRepository; + } + + @Transactional + public Course save(Course course) { + return courseRepository.save(course); + } + + @Transactional(readOnly = true) + public Course findById(Long id) { + return courseRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Course with id: " + id + " was not found.")); + } + + @Transactional(readOnly = true) + public Page<Course> findAll(Pageable pageable) { + return courseRepository.findAll(pageable); + } + + @Transactional + public Course update(Long id, Course newCourse) { + Optional<Course> optionalCourse = courseRepository.findById(id); + if (optionalCourse.isPresent()) { + Course course = optionalCourse.get(); + course.setName(newCourse.getName()); + course.setCapacity(newCourse.getCapacity()); + course.setLanguage(newCourse.getLanguage()); + course.setProficiency(newCourse.getProficiency()); + return courseRepository.save(course); + } else { + throw new ResourceNotFoundException("Course with id: " + id + " was not found."); + } + } + + @Transactional + public void delete(Long id) { + courseRepository.deleteById(id); + } +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserController.java index d0c6429b..6164c8e6 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserController.java @@ -25,13 +25,13 @@ public class UserController { private final UserMapper mapper; @Autowired - public UserController(UserService service/*, UserMapper mapper*/) { + public UserController(UserService service, UserMapper mapper) { this.service = service; - this.mapper = null; + this.mapper = mapper; } @GetMapping("/{id}") - public UserDto find(@PathVariable String id) { + public UserDto find(@PathVariable Long id) { return new UserDto("spracher","spracher@gmail.com","Sprach","MeNot",new AddressDto()); } @@ -41,12 +41,12 @@ public class UserController { } @DeleteMapping("/{id}") - public UserDto deleteUser(@PathVariable String id) { + public UserDto deleteUser(@PathVariable Long id) { return new UserDto("spracher","spracher@gmail.com","Sprach","MeNot",new AddressDto()); } @PutMapping("/update/{id}") - public UserDto update(@PositiveOrZero @PathVariable String id,@Valid @RequestBody UserCreateDto user) { + public UserDto update(@PositiveOrZero @PathVariable Long id,@Valid @RequestBody UserCreateDto user) { return new UserDto(user.getUsername(),user.getEmail(),user.getFirstName(),user.getLastName(),user.getAddress()); } @@ -68,22 +68,22 @@ public class UserController { @PostMapping("/logout/{id}") - public String logout(@PathVariable String id) { + public String logout(@PathVariable Long id) { return "user has logged out"; } @GetMapping("/finished/{id}") - public List<Course> getFinished(@PathVariable String id) { + public List<Course> getFinished(@PathVariable Long id) { return new ArrayList<>(); } @GetMapping("/enrolled/{id}") - public List<Course> getEnrolled(@PathVariable String id) { + public List<Course> getEnrolled(@PathVariable Long id) { return new ArrayList<>(); } @PutMapping("/addLanguage/{id}") - public String addLanguage(@PathVariable String id,@Valid @RequestBody UserAddLanguageDto body) { + public String addLanguage(@PathVariable Long id,@Valid @RequestBody UserAddLanguageDto body) { return "added Absolutely Nothing successfully!"; } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserRepository.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserRepository.java index 32d06b13..f649fbd4 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserRepository.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserRepository.java @@ -4,5 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface UserRepository extends JpaRepository<User, String> { +public interface UserRepository extends JpaRepository<User, Long> { } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserService.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserService.java index 1fc03b8c..12853d56 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserService.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/user/UserService.java @@ -19,7 +19,7 @@ public class UserService extends DomainService<User> { } @Transactional(readOnly = true) - public User find(String id) { + public User find(Long id) { return repository.findById(id) .orElseThrow(() -> new EntityNotFoundException("User '" + id + "' not found.")); } diff --git a/application/module-language-school/src/main/resources/application.properties b/application/module-language-school/src/main/resources/application.properties index 888dcec4..08d8fcc5 100644 --- a/application/module-language-school/src/main/resources/application.properties +++ b/application/module-language-school/src/main/resources/application.properties @@ -1 +1,13 @@ -server.port=5000 \ No newline at end of file +server.port=5000 + +spring.jpa.open-in-view=false +spring.datasource.url=jdbc:h2:mem:social-network;MODE=PostgreSQL +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=SedaQ-app +spring.datasource.password=$argon2id$v=19$m=16,t=2,p=1$YmF0bWFuYmF0bWFu$MdHYB359HdivAb9J6CaILw +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +# showing SQL is generally good practice for running project locally to check whether there is not an issue with implementation of JPA methods. +spring.jpa.show-sql=true +spring.jackson.property-naming-strategy=LOWER_CAMEL_CASE +spring.cache.type=NONE +appconfig.enablecache=false \ No newline at end of file diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java index e4fd8f0e..05add7ac 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java @@ -14,6 +14,7 @@ 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.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -21,7 +22,8 @@ import java.util.ArrayList; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -29,15 +31,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc public class CourseTest { + + private final CourseCreateDto courseCreateDto = new CourseCreateDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + private final CourseDto courseDto = new CourseDto(0L, "english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); @Autowired ObjectMapper objectMapper; @Autowired private MockMvc mockMvc; + @MockBean private CourseController courseController; - private final CourseCreateDto courseCreateDto = new CourseCreateDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2);; - private final CourseDto courseDto = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + public static String asJsonString(final Object obj) { + try { + return new ObjectMapper().writeValueAsString(obj); + } catch (Exception e) { + throw new RuntimeException(e); + } + } @Test void createCourse() throws Exception { @@ -48,8 +59,8 @@ public class CourseTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("english b2 course")) .andExpect(jsonPath("$.capacity").value(10)) - .andExpect(jsonPath("$.languageTypeDto").value("ENGLISH")) - .andExpect(jsonPath("$.proficiencyLevelDto").value("B2")) + .andExpect(jsonPath("$.language").value("ENGLISH")) + .andExpect(jsonPath("$.proficiency").value("B2")) .andExpect(jsonPath("$.id").exists()) .andReturn().getResponse().getContentAsString(); } @@ -74,21 +85,21 @@ public class CourseTest { @Test void findCourse() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(courseController.find(id)).thenReturn(courseDto); mockMvc.perform(get("/courses/find/" + id)) .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(id)) .andExpect(jsonPath("$.name").value("english b2 course")) .andExpect(jsonPath("$.capacity").value(10)) - .andExpect(jsonPath("$.languageTypeDto").value("ENGLISH")) - .andExpect(jsonPath("$.proficiencyLevelDto").value("B2")) - .andExpect(jsonPath("$.id").value(id)) + .andExpect(jsonPath("$.language").value("ENGLISH")) + .andExpect(jsonPath("$.proficiency").value("B2")) .andReturn().getResponse().getContentAsString(); } @Test void findCourseWithoutId() throws Exception { - Mockito.when(courseController.find(ArgumentMatchers.anyString())).thenReturn(courseDto); + Mockito.when(courseController.find(ArgumentMatchers.anyLong())).thenReturn(courseDto); mockMvc.perform(get("/courses/find/")) .andExpect(status().is4xxClientError()); } @@ -96,18 +107,18 @@ public class CourseTest { @Test void findAll() throws Exception { int page = 0; - Mockito.when(courseController.findAll(page)).thenReturn(new ArrayList<>()); + Mockito.when(courseController.findAll(page)).thenReturn(new PageImpl<>(new ArrayList<>())); String response = mockMvc.perform(get("/courses/findAll").param("page", Integer.toString(page))) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); - assertThat("response", response, is("[]")); + assertTrue(response.contains("\"content\":[]")); } @Test void findAllWithoutPage() throws Exception { - Mockito.when(courseController.findAll(ArgumentMatchers.anyInt())).thenReturn(new ArrayList<>()); - mockMvc.perform(get("/courses/findAll")) +// Mockito.when(courseController.findAll(ArgumentMatchers.anyInt())).thenReturn(new PageImpl<>(new ArrayList<>())); + mockMvc.perform(get("/courses/findAll")) .andExpect(status().is4xxClientError()); } @@ -128,7 +139,7 @@ public class CourseTest { @Test void findAllByLangWithoutParameters() throws Exception { Mockito.when(courseController.findAll(ArgumentMatchers.anyInt(), - ArgumentMatchers.isA(LanguageTypeDto.class))) + ArgumentMatchers.isA(LanguageTypeDto.class))) .thenReturn(new ArrayList<>()); mockMvc.perform(get("/courses/findAllByLang")) .andExpect(status().is4xxClientError()); @@ -138,7 +149,7 @@ public class CourseTest { void findAllByLangWithoutLang() throws Exception { Mockito.when(courseController.findAll(ArgumentMatchers.anyInt(), ArgumentMatchers.isA(LanguageTypeDto.class))) - .thenReturn(new ArrayList<>()); + .thenReturn(new ArrayList<>()); String page = "0"; mockMvc.perform(get("/courses/findAllByLang").param("page", page)) .andExpect(status().is4xxClientError()); @@ -183,26 +194,26 @@ public class CourseTest { @Test void updateCourse() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(courseController.update(ArgumentMatchers.eq(id), - ArgumentMatchers.isA(CourseCreateDto.class))) + ArgumentMatchers.isA(CourseCreateDto.class))) .thenReturn(courseDto); mockMvc.perform(put("/courses/update/" + id) - .content(asJsonString(courseDto)) - .contentType(MediaType.APPLICATION_JSON)) + .content(asJsonString(courseDto)) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("english b2 course")) .andExpect(jsonPath("$.capacity").value(10)) - .andExpect(jsonPath("$.languageTypeDto").value("ENGLISH")) - .andExpect(jsonPath("$.proficiencyLevelDto").value("B2")) + .andExpect(jsonPath("$.language").value("ENGLISH")) + .andExpect(jsonPath("$.proficiency").value("B2")) .andExpect(jsonPath("$.id").value(courseDto.getId())) .andReturn().getResponse().getContentAsString(); } @Test void updateCourseWithoutParameter() throws Exception { - Mockito.when(courseController.update(ArgumentMatchers.anyString(), + Mockito.when(courseController.update(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(CourseCreateDto.class))) .thenReturn(courseDto); mockMvc.perform(put("/courses/update")) @@ -211,7 +222,7 @@ public class CourseTest { @Test void deleteCourse() throws Exception { - String id = "0"; + Long id = 0L; Mockito.doNothing().when(courseController).delete(id); mockMvc.perform(delete("/courses/delete/" + id)) @@ -220,7 +231,7 @@ public class CourseTest { @Test void deleteCourseWithoutParameter() throws Exception { - Mockito.doNothing().when(courseController).delete(ArgumentMatchers.anyString()); + Mockito.doNothing().when(courseController).delete(ArgumentMatchers.anyLong()); mockMvc.perform(delete("/courses/delete/")) .andExpect(status().is4xxClientError()); @@ -228,11 +239,11 @@ public class CourseTest { @Test void enrolCourse() throws Exception { - String id = "0"; - UserDto student = new UserDto("novakovat","novakova@gamil.com", "Tereza", + Long id = 0L; + UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto()); - CourseDto courseDtoWithStudent = new CourseDto("english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + CourseDto courseDtoWithStudent = new CourseDto(id, "english b2 course", 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); courseDtoWithStudent.setStudentIds(new ArrayList<>(List.of(student.getId()))); Mockito.when(courseController.enrol(ArgumentMatchers.eq(id), @@ -244,16 +255,16 @@ public class CourseTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("english b2 course")) .andExpect(jsonPath("$.capacity").value(10)) - .andExpect(jsonPath("$.languageTypeDto").value("ENGLISH")) - .andExpect(jsonPath("$.proficiencyLevelDto").value("B2")) - .andExpect(jsonPath("$.studentIds").value(student.getId())) + .andExpect(jsonPath("$.language").value("ENGLISH")) + .andExpect(jsonPath("$.proficiency").value("B2")) + .andExpect(jsonPath("$.studentIds").exists()) .andReturn().getResponse().getContentAsString(); } @Test void enrolCourseWithoutUserParameter() throws Exception { String id = "0"; - Mockito.when(courseController.enrol(ArgumentMatchers.anyString(), + Mockito.when(courseController.enrol(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(UserDto.class))) .thenReturn(courseDto); mockMvc.perform(patch("/courses/enrol/" + id)) @@ -262,26 +273,26 @@ public class CourseTest { @Test void enrolCourseWithoutCourseIdParameter() throws Exception { - Mockito.when(courseController.enrol(ArgumentMatchers.anyString(), + Mockito.when(courseController.enrol(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(UserDto.class))) .thenReturn(courseDto); - UserDto student = new UserDto("novakovat","novakova@gamil.com", "Tereza", + UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto()); mockMvc.perform(patch("/courses/enrol/") - .content(asJsonString(student)) - .contentType(MediaType.APPLICATION_JSON)) + .content(asJsonString(student)) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); } @Test void expelCourse() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(courseController.expel(ArgumentMatchers.eq(id), ArgumentMatchers.isA(UserDto.class))) .thenReturn(courseDto); - UserDto student = new UserDto("novakovat","novakova@gamil.com", "Tereza", + UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto()); mockMvc.perform(patch("/courses/expel/" + id) @@ -290,15 +301,15 @@ public class CourseTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("english b2 course")) .andExpect(jsonPath("$.capacity").value(10)) - .andExpect(jsonPath("$.languageTypeDto").value("ENGLISH")) - .andExpect(jsonPath("$.proficiencyLevelDto").value("B2")) + .andExpect(jsonPath("$.language").value("ENGLISH")) + .andExpect(jsonPath("$.proficiency").value("B2")) .andExpect(jsonPath("$.studentIds").isEmpty()) .andReturn().getResponse().getContentAsString(); } @Test void expelCourseWithoutUserParameter() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(courseController.expel(ArgumentMatchers.eq(id), ArgumentMatchers.isA(UserDto.class))) .thenReturn(courseDto); @@ -309,10 +320,10 @@ public class CourseTest { @Test void deleteCourseWithoutCourseIdParameter() throws Exception { - Mockito.when(courseController.expel(ArgumentMatchers.anyString(), + Mockito.when(courseController.expel(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(UserDto.class))) .thenReturn(courseDto); - UserDto student = new UserDto("novakovat","novakova@gamil.com", "Tereza", + UserDto student = new UserDto("novakovat", "novakova@gamil.com", "Tereza", "Nováková", new AddressDto()); mockMvc.perform(patch("/courses/expel/") @@ -320,12 +331,4 @@ public class CourseTest { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); } - - public static String asJsonString(final Object obj) { - try { - return new ObjectMapper().writeValueAsString(obj); - } catch (Exception e) { - throw new RuntimeException(e); - } - } } diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/user/UserControllerTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/user/UserControllerTest.java index 85aa5f33..59f5032b 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/user/UserControllerTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/user/UserControllerTest.java @@ -121,7 +121,7 @@ class UserControllerTest { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - String id = objectMapper.readValue(response, UserDto.class).getId(); + Long id = objectMapper.readValue(response, UserDto.class).getId(); mockMvc.perform(get("/users/{id}", id)) .andExpect(status().isOk()) @@ -142,7 +142,7 @@ class UserControllerTest { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - String id = objectMapper.readValue(response, UserDto.class).getId(); + Long id = objectMapper.readValue(response, UserDto.class).getId(); mockMvc.perform(delete("/users/{id}", id) .contentType(MediaType.APPLICATION_JSON)) @@ -156,7 +156,7 @@ class UserControllerTest { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - String id = objectMapper.readValue(response, UserDto.class).getId(); + Long id = objectMapper.readValue(response, UserDto.class).getId(); var updatedUsername = "novak"; var userToUpdate = new UserCreateDto( @@ -195,7 +195,7 @@ class UserControllerTest { .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); - String id = objectMapper.readValue(response, UserDto.class).getId(); + Long id = objectMapper.readValue(response, UserDto.class).getId(); mockMvc.perform(post("/users/logout/{id}", id)) .andExpect(status().isOk()); @@ -203,19 +203,19 @@ class UserControllerTest { @Test void getFinished() throws Exception { - mockMvc.perform(get("/users/finished/{id}", "1c1bbf66-6585-4978-886b-b126335ff3af")) + mockMvc.perform(get("/users/finished/1", "1c1bbf66-6585-4978-886b-b126335ff3af")) .andExpect(status().isOk()); } @Test void getEnrolled() throws Exception { - mockMvc.perform(get("/users/enrolled/{id}", "1c1bbf66-6585-4978-886b-b126335ff3af")) + mockMvc.perform(get("/users/enrolled/1", "1c1bbf66-6585-4978-886b-b126335ff3af")) .andExpect(status().isOk()); } @Test void addLanguage() throws Exception { - mockMvc.perform(put("/users/addLanguage/{id}", "1c1bbf66-6585-4978-886b-b126335ff3af") + mockMvc.perform(put("/users/addLanguage/1", "1c1bbf66-6585-4978-886b-b126335ff3af") .content(asJsonString(new UserAddLanguageDto(LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2))) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); diff --git a/application/module-language-school/src/test/resources/application-test.properties b/application/module-language-school/src/test/resources/application-test.properties new file mode 100644 index 00000000..896213ef --- /dev/null +++ b/application/module-language-school/src/test/resources/application-test.properties @@ -0,0 +1,14 @@ +# For description of each field check: https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# INIT=CREATE SCHEMA IF NOT EXISTS PA165;SET SCHEMA PA165 +spring.datasource.url=jdbc:h2:mem:testdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.username=SedaQ-app-test +spring.datasource.password= +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.hibernate.ddl-auto=create +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.properties.hibernate.generate_statistics=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.show_sql=false +spring.h2.console.enabled=true +spring.cache.type=NONE +appconfig.enablecache=false \ No newline at end of file diff --git a/application/module-language-school/src/test/resources/logback.xml b/application/module-language-school/src/test/resources/logback.xml new file mode 100644 index 00000000..e449f9cd --- /dev/null +++ b/application/module-language-school/src/test/resources/logback.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d %5p %40.40c:%4L - %m%n</pattern> + </encoder> + </appender> + + <root level="info"> + <appender-ref ref="console" /> + </root> +<!-- TODO remove solution--> + <logger name="org.hibernate.SQL" level="DEBUG"/> + +</configuration> \ No newline at end of file -- GitLab From 6cc75aa61d5b061a8df5ed5e9ff2b61c38a24d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <xpokorn8@fi.muni.cz> Date: Wed, 5 Apr 2023 22:05:18 +0200 Subject: [PATCH 2/4] Course implementation --- .../model/dto/common/DomainObjectDto.java | 2 -- .../modulelanguageschool/course/Course.java | 20 +++++++++--- .../course/CourseController.java | 18 ++++------- .../course/CourseFacade.java | 26 ++++++++++++++- .../course/CourseRepository.java | 8 +++++ .../course/CourseService.java | 32 +++++++++++++++++++ .../course/CourseTest.java | 25 +++------------ 7 files changed, 92 insertions(+), 39 deletions(-) diff --git a/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java b/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java index 36ec98b0..7824c73c 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/common/DomainObjectDto.java @@ -1,6 +1,5 @@ package org.fuseri.model.dto.common; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @@ -9,7 +8,6 @@ import lombok.Setter; @Setter public abstract class DomainObjectDto { - @NotBlank @NotNull private Long id; } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java index 455637c7..99e1ff83 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/Course.java @@ -1,11 +1,12 @@ package org.fuseri.modulelanguageschool.course; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.*; import org.fuseri.modulelanguageschool.common.DomainObject; +import org.fuseri.modulelanguageschool.user.User; + +import java.util.List; +import java.util.Set; @Getter @Setter @@ -23,4 +24,15 @@ public class Course extends DomainObject { @Enumerated(EnumType.STRING) private ProficiencyLevel proficiency; + + @ManyToMany + private Set<User> students; + + public void enrolStudent(User student) { + students.add(student); + } + + public void expelStudent(User student) { + students.remove(student); + } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java index 75e92aa7..6d11099e 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java @@ -23,7 +23,6 @@ import java.util.List; @RequestMapping("/courses") public class CourseController { - public static final String COURSE_NAME = "english b2 course"; private final CourseFacade courseFacade; @Autowired @@ -67,28 +66,25 @@ public class CourseController { /** * Retrieves a paginated list of courses of a given language * - * @param page the page number to retrieve * @param lang the language to find courses of * @return the Result containing the requested page of CourseDtos */ @GetMapping("/findAllByLang") - public List<CourseDto> findAll(@RequestParam int page, @RequestParam LanguageTypeDto lang) { - return new ArrayList<>(); + public List<CourseDto> findAll(@RequestParam LanguageTypeDto lang) { + return courseFacade.findAll(lang); } /** * Retrieves a paginated list of courses of a given language and proficiency * - * @param page the page number to retrieve * @param lang the language to find courses of * @param prof the proficiency of the language * @return the Result containing the requested page of CourseDtos */ @GetMapping("/findAllByLangProf") - public List<CourseDto> findAll(@RequestParam int page, - @RequestParam LanguageTypeDto lang, + public List<CourseDto> findAll(@RequestParam LanguageTypeDto lang, @RequestParam ProficiencyLevelDto prof) { - return new ArrayList<>(); + return courseFacade.findAll(lang, prof); } /** @@ -123,9 +119,7 @@ public class CourseController { */ @PatchMapping("/enrol/{id}") public CourseDto enrol(@PathVariable Long id, @RequestBody UserDto student) { - var course = new CourseDto(0L, COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); - course.setStudentIds(new ArrayList<>(List.of(student.getId()))); - return course; + return courseFacade.enrol(id, student); } /** @@ -137,7 +131,7 @@ public class CourseController { */ @PatchMapping("/expel/{id}") public CourseDto expel(@PathVariable Long id, @RequestBody UserDto student) { - return new CourseDto(0L, COURSE_NAME, 10, LanguageTypeDto.ENGLISH, ProficiencyLevelDto.B2); + return courseFacade.expel(id, student); } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java index 9899db25..db7f28aa 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseFacade.java @@ -2,6 +2,10 @@ package org.fuseri.modulelanguageschool.course; import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; +import org.fuseri.model.dto.course.LanguageTypeDto; +import org.fuseri.model.dto.course.ProficiencyLevelDto; +import org.fuseri.model.dto.user.UserDto; +import org.fuseri.modulelanguageschool.user.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; @@ -9,16 +13,20 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @Transactional public class CourseFacade { private final CourseService courseService; private final CourseMapper courseMapper; + private final UserMapper userMapper; @Autowired - public CourseFacade(CourseService courseService, CourseMapper courseMapper) { + public CourseFacade(CourseService courseService, CourseMapper courseMapper, UserMapper userMapper) { this.courseService = courseService; this.courseMapper = courseMapper; + this.userMapper = userMapper; } @Transactional @@ -46,4 +54,20 @@ public class CourseFacade { public void delete(Long id) { courseService.delete(id); } + + public List<CourseDto> findAll(LanguageTypeDto lang) { + return courseMapper.mapToList(courseService.findAll(Language.valueOf(lang.name()))); + } + + public List<CourseDto> findAll(LanguageTypeDto lang, ProficiencyLevelDto prof) { + return courseMapper.mapToList(courseService.findAll(Language.valueOf(lang.name()), ProficiencyLevel.valueOf(prof.name()))); + } + + public CourseDto enrol(Long id, UserDto student) { + return courseMapper.mapToDto(courseService.enrol(id, userMapper.fromDto(student))); + } + + public CourseDto expel(Long id, UserDto student) { + return courseMapper.mapToDto(courseService.expel(id, userMapper.fromDto(student))); + } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java index 1dcad446..4ea630cc 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseRepository.java @@ -1,9 +1,17 @@ package org.fuseri.modulelanguageschool.course; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface CourseRepository extends JpaRepository<Course, Long> { + @Query("SELECT c FROM Course c WHERE c.language = ?1") + List<Course> findAllByLang(Language language); + + @Query("SELECT c FROM Course c WHERE c.language = ?1 AND c.proficiency = ?2") + List<Course> findAllByLangProf(Language language, ProficiencyLevel proficiencyLevel); } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java index eb206585..13079299 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseService.java @@ -1,12 +1,14 @@ package org.fuseri.modulelanguageschool.course; import org.fuseri.modulelanguageschool.common.ResourceNotFoundException; +import org.fuseri.modulelanguageschool.user.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.Optional; @Service @@ -54,4 +56,34 @@ public class CourseService { public void delete(Long id) { courseRepository.deleteById(id); } + + public List<Course> findAll(Language language) { + return courseRepository.findAllByLang(language); + } + + public List<Course> findAll(Language language, ProficiencyLevel proficiencyLevel) { + return courseRepository.findAllByLangProf(language, proficiencyLevel); + } + + public Course enrol(Long id, User student) { + Optional<Course> optionalCourse = courseRepository.findById(id); + if (optionalCourse.isPresent()) { + Course course = optionalCourse.get(); + course.enrolStudent(student); + return courseRepository.save(course); + } else { + throw new ResourceNotFoundException("Course with id: " + id + " was not found."); + } + } + + public Course expel(Long id, User student) { + Optional<Course> optionalCourse = courseRepository.findById(id); + if (optionalCourse.isPresent()) { + Course course = optionalCourse.get(); + course.expelStudent(student); + return courseRepository.save(course); + } else { + throw new ResourceNotFoundException("Course with id: " + id + " was not found."); + } + } } diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java index 05add7ac..bab37001 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java @@ -126,7 +126,7 @@ public class CourseTest { void findAllByLang() throws Exception { int page = 0; LanguageTypeDto lang = LanguageTypeDto.ENGLISH; - Mockito.when(courseController.findAll(page, lang)).thenReturn(new ArrayList<>()); + Mockito.when(courseController.findAll(lang)).thenReturn(new ArrayList<>()); String response = mockMvc.perform(get("/courses/findAllByLang") .param("page", Integer.toString(page)) .param("lang", lang.toString())) @@ -136,33 +136,18 @@ public class CourseTest { assertThat("response", response, is("[]")); } - @Test - void findAllByLangWithoutParameters() throws Exception { - Mockito.when(courseController.findAll(ArgumentMatchers.anyInt(), - ArgumentMatchers.isA(LanguageTypeDto.class))) - .thenReturn(new ArrayList<>()); - mockMvc.perform(get("/courses/findAllByLang")) - .andExpect(status().is4xxClientError()); - } - @Test void findAllByLangWithoutLang() throws Exception { - Mockito.when(courseController.findAll(ArgumentMatchers.anyInt(), - ArgumentMatchers.isA(LanguageTypeDto.class))) - .thenReturn(new ArrayList<>()); - String page = "0"; - mockMvc.perform(get("/courses/findAllByLang").param("page", page)) + mockMvc.perform(get("/courses/findAllByLang")) .andExpect(status().is4xxClientError()); } @Test void findAllByLangProf() throws Exception { - int page = 0; LanguageTypeDto lang = LanguageTypeDto.ENGLISH; ProficiencyLevelDto proficiencyLevel = ProficiencyLevelDto.A1; - Mockito.when(courseController.findAll(page, lang, proficiencyLevel)).thenReturn(new ArrayList<>()); + Mockito.when(courseController.findAll(lang, proficiencyLevel)).thenReturn(new ArrayList<>()); String response = mockMvc.perform(get("/courses/findAllByLangProf") - .param("page", Integer.toString(page)) .param("lang", lang.toString()) .param("prof", proficiencyLevel.toString())) .andExpect(status().isOk()) @@ -173,7 +158,7 @@ public class CourseTest { @Test void findAllByLangProfWithoutParameters() throws Exception { - Mockito.when(courseController.findAll(ArgumentMatchers.anyInt(), + Mockito.when(courseController.findAll( ArgumentMatchers.isA(LanguageTypeDto.class), ArgumentMatchers.isA(ProficiencyLevelDto.class))) .thenReturn(new ArrayList<>()); @@ -183,7 +168,7 @@ public class CourseTest { @Test void findAllByLangProfWithoutLangProf() throws Exception { - Mockito.when(courseController.findAll(ArgumentMatchers.anyInt(), + Mockito.when(courseController.findAll( ArgumentMatchers.isA(LanguageTypeDto.class), ArgumentMatchers.isA(ProficiencyLevelDto.class))) .thenReturn(new ArrayList<>()); -- GitLab From e54e5b84cc71dcdf8f2b03149b654037bb031508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <xpokorn8@fi.muni.cz> Date: Fri, 7 Apr 2023 11:59:12 +0200 Subject: [PATCH 3/4] Lecture implementation --- .../model/dto/lecture/LectureCreateDto.java | 7 +- .../fuseri/model/dto/lecture/LectureDto.java | 10 +- .../modulelanguageschool/lecture/Lecture.java | 18 +++- .../lecture/LectureController.java | 54 +++++----- .../lecture/LectureFacade.java | 78 ++++++++++++++ .../lecture/LectureMapper.java | 25 +++++ .../lecture/LectureRepository.java | 21 ++++ .../lecture/LectureService.java | 102 ++++++++++++++++++ .../lecture/LectureTest.java | 34 +++--- 9 files changed, 294 insertions(+), 55 deletions(-) create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureMapper.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureRepository.java create mode 100644 application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureService.java diff --git a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java index 6fc9b766..0279180b 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureCreateDto.java @@ -3,7 +3,6 @@ package org.fuseri.model.dto.lecture; import jakarta.validation.constraints.*; import lombok.Getter; import lombok.Setter; -import org.fuseri.model.dto.course.CourseDto; import java.time.LocalDateTime; @@ -27,10 +26,10 @@ public class LectureCreateDto { @Min(value = 1, message = "Lecture capacity must be at least 1") private Integer capacity; - @NotBlank(message = "Lecture course cannot be blank") - private String courseId; + @NotNull(message = "Lecture course cannot be null") + private Long courseId; - public LectureCreateDto(LocalDateTime from, LocalDateTime to, String topic, Integer capacity, String courseId) { + public LectureCreateDto(LocalDateTime from, LocalDateTime to, String topic, Integer capacity, Long courseId) { this.from = from; this.to = to; this.topic = topic; diff --git a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java index f54ac383..df35d83c 100644 --- a/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java +++ b/application/model/src/main/java/org/fuseri/model/dto/lecture/LectureDto.java @@ -29,16 +29,16 @@ public class LectureDto extends DomainObjectDto { @Min(value = 1, message = "Lecture capacity must be at least 1") private Integer capacity; - @NotNull(message = "Lecture capacity cannot be null") - private String lecturerId; + @NotNull(message = "Lecture lecturer cannot be null") + private Long lecturerId; - @NotBlank(message = "Lecture courseId cannot be blank") - private String courseId; + @NotNull(message = "Lecture courseId cannot be null") + private Long courseId; @NotNull(message = "Student IDs list cannot be null") private List<Long> studentIds; - public LectureDto(LocalDateTime from, LocalDateTime to, String topic, Integer capacity, String lecturerId, String courseId) { + public LectureDto(LocalDateTime from, LocalDateTime to, String topic, Integer capacity, Long lecturerId, Long courseId) { this.from = from; this.to = to; this.topic = topic; diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/Lecture.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/Lecture.java index 86b9670a..a719f242 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/Lecture.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/Lecture.java @@ -3,6 +3,7 @@ package org.fuseri.modulelanguageschool.lecture; import jakarta.persistence.Entity; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,13 +13,15 @@ import org.fuseri.modulelanguageschool.course.Course; import org.fuseri.modulelanguageschool.user.User; import java.time.LocalDateTime; -import java.util.List; +import java.util.Set; /** * This class represents a lecture entity in the domain model. */ @Getter @Setter +@Entity +@Table(name = "lecture") @NoArgsConstructor @AllArgsConstructor public class Lecture extends DomainObject { @@ -27,9 +30,20 @@ public class Lecture extends DomainObject { private LocalDateTime to; private String topic; + @ManyToOne private Course course; + @ManyToOne private User lecturer; - private List<User> user; + @ManyToMany + private Set<User> students; + + public void enrolStudent(User student) { + students.add(student); + } + + public void expelStudent(User student) { + students.remove(student); + } } diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java index 34d91578..99da62d6 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java @@ -1,15 +1,12 @@ package org.fuseri.modulelanguageschool.lecture; import jakarta.validation.Valid; -import org.fuseri.model.dto.course.CourseDto; import org.fuseri.model.dto.lecture.LectureCreateDto; import org.fuseri.model.dto.lecture.LectureDto; import org.fuseri.model.dto.user.UserDto; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import java.time.LocalDateTime; -import java.time.Month; -import java.util.ArrayList; import java.util.List; /** @@ -20,9 +17,13 @@ import java.util.List; @RequestMapping("/lectures") public class LectureController { - private static final LocalDateTime START_DATETIME = LocalDateTime.of(2045, Month.JUNE, 30, 12, 0, 0); - private static final LocalDateTime END_DATETIME = LocalDateTime.of(2045, Month.JUNE, 30, 14, 0, 0); - public static final String TOPIC = "Learning how to spell deprecated"; + private final LectureFacade lectureFacade; + + @Autowired + public LectureController(LectureFacade lectureFacade) { + this.lectureFacade = lectureFacade; + } + /** * Creates a new lecture resource by delegating to the LectureService's create method. @@ -32,18 +33,18 @@ public class LectureController { */ @PostMapping("/create") public LectureDto create(@Valid @RequestBody LectureCreateDto lecture) { - return new LectureDto(START_DATETIME, END_DATETIME, TOPIC, 10, "0", "0"); + return lectureFacade.create(lecture); } /** * Retrieves a lecture resource by its ID. * - * @param id the ID of the lecture to find + * @param courseId the ID of the lecture to find * @return the LectureDto representing the found lecture */ - @GetMapping("find/{id}") - public LectureDto find(@PathVariable String id) { - return new LectureDto(START_DATETIME, END_DATETIME, TOPIC, 10, "0", "0"); + @GetMapping("find/{courseId}") + public LectureDto find(@PathVariable Long courseId) { + return lectureFacade.findById(courseId); } /** @@ -53,8 +54,8 @@ public class LectureController { * @return the list of LectureDtos */ @GetMapping("/findByCourse") - public List<LectureDto> findByCourse(@Valid @RequestParam String courseId) { - return new ArrayList<>(); + public List<LectureDto> findByCourse(@Valid @RequestParam Long courseId) { + return lectureFacade.findAll(courseId); } /** @@ -64,8 +65,8 @@ public class LectureController { * @return the LectureDto representing the updated lecture */ @PutMapping("/update/{id}") - public LectureDto update(@PathVariable String id, @Valid @RequestBody LectureCreateDto lecture) { - return new LectureDto(START_DATETIME, END_DATETIME, TOPIC, 10, "0", "0"); + public LectureDto update(@PathVariable Long id, @Valid @RequestBody LectureCreateDto lecture) { + return lectureFacade.update(id, lecture); } /** @@ -74,20 +75,21 @@ public class LectureController { * @param id the ID of the lecture to delete */ @DeleteMapping("/delete/{id}") - public void delete(@PathVariable String id) { + public void delete(@PathVariable Long id) { + lectureFacade.delete(id); } /** * Adds lecturer to the existing lecture resource * - * @param id id of lecture to update - * @param lecturer UserDto for the course lecturer + * @param id id of lecture to update + * @param lecturerDto UserDto for the course lecturer * @return the LectureDto representing the updated lecture */ @PatchMapping("/setLecturer/{id}") - public LectureDto setLecturer(@PathVariable String id, @RequestBody UserDto lecturer) { - return new LectureDto(START_DATETIME, END_DATETIME, TOPIC, 10, "0", "0"); + public LectureDto setLecturer(@PathVariable Long id, @RequestBody UserDto lecturerDto) { + return lectureFacade.setLecturer(id, lecturerDto); } /** @@ -98,10 +100,8 @@ public class LectureController { * @return the LectureDto representing the updated lecture */ @PatchMapping("/enrol/{id}") - public LectureDto enrol(@PathVariable String id, @RequestBody UserDto student) { - var lecture = new LectureDto(START_DATETIME, END_DATETIME, TOPIC, 10, "0", "0"); - lecture.setStudentIds(new ArrayList<>(List.of(student.getId()))); - return lecture; + public LectureDto enrol(@PathVariable Long id, @RequestBody UserDto student) { + return lectureFacade.enrol(id, student); } /** @@ -112,7 +112,7 @@ public class LectureController { * @return the LectureDto representing the updated lecture */ @PatchMapping("/expel/{id}") - public LectureDto expel(@PathVariable String id, @RequestBody UserDto student) { - return new LectureDto(START_DATETIME, END_DATETIME, TOPIC, 10, "0", "0"); + public LectureDto expel(@PathVariable Long id, @RequestBody UserDto student) { + return lectureFacade.expel(id, student); } } \ No newline at end of file diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java new file mode 100644 index 00000000..08fe710c --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureFacade.java @@ -0,0 +1,78 @@ +package org.fuseri.modulelanguageschool.lecture; + +import org.fuseri.model.dto.course.LanguageTypeDto; +import org.fuseri.model.dto.course.ProficiencyLevelDto; +import org.fuseri.model.dto.lecture.LectureCreateDto; +import org.fuseri.model.dto.lecture.LectureDto; +import org.fuseri.model.dto.user.UserDto; +import org.fuseri.modulelanguageschool.course.Language; +import org.fuseri.modulelanguageschool.course.ProficiencyLevel; +import org.fuseri.modulelanguageschool.user.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +public class LectureFacade { + + private final LectureService lectureService; + private final LectureMapper lectureMapper; + private final UserMapper userMapper; + + @Autowired + public LectureFacade(LectureService lectureService, LectureMapper lectureMapper, UserMapper userMapper) { + this.lectureService = lectureService; + this.lectureMapper = lectureMapper; + this.userMapper = userMapper; + } + + @Transactional + public LectureDto create(LectureCreateDto dto) { + return lectureMapper.mapToDto(lectureService.save(lectureMapper.mapToLecture(dto))); + } + + @Cacheable(cacheNames = "courses", key = "#id") + @Transactional(readOnly = true) + public LectureDto findById(Long id) { + return lectureMapper.mapToDto(lectureService.findById(id)); + } + + @Transactional(readOnly = true) + public List<LectureDto> findAll(Long id) { + return lectureMapper.mapToList(lectureService.findAllByCourse(id)); + } + + @Transactional + public LectureDto update(Long id, LectureCreateDto dto) { + return lectureMapper.mapToDto(lectureService.update(id, lectureMapper.mapToLecture(dto))); + } + + @Transactional + public void delete(Long id) { + lectureService.delete(id); + } + + public List<LectureDto> findAll(LanguageTypeDto lang) { + return lectureMapper.mapToList(lectureService.findAll(Language.valueOf(lang.name()))); + } + + public List<LectureDto> findAll(LanguageTypeDto lang, ProficiencyLevelDto prof) { + return lectureMapper.mapToList(lectureService.findAll(Language.valueOf(lang.name()), ProficiencyLevel.valueOf(prof.name()))); + } + + public LectureDto enrol(Long id, UserDto student) { + return lectureMapper.mapToDto(lectureService.enrol(id, userMapper.fromDto(student))); + } + + public LectureDto expel(Long id, UserDto student) { + return lectureMapper.mapToDto(lectureService.expel(id, userMapper.fromDto(student))); + } + + public LectureDto setLecturer(Long id, UserDto lecturerDto) { + return lectureMapper.mapToDto(lectureService.setLecturer(id, userMapper.fromDto(lecturerDto))); + } +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureMapper.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureMapper.java new file mode 100644 index 00000000..8136a3e2 --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureMapper.java @@ -0,0 +1,25 @@ +package org.fuseri.modulelanguageschool.lecture; + +import org.fuseri.model.dto.lecture.LectureCreateDto; +import org.fuseri.model.dto.lecture.LectureDto; +import org.mapstruct.Mapper; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface LectureMapper { + LectureDto mapToDto(Lecture lecture); + + Lecture mapToLecture(LectureDto lectureDto); + + List<LectureDto> mapToList(List<Lecture> lectures); + + + default Page<LectureDto> mapToPageDto(Page<Lecture> lectures) { + return new PageImpl<>(mapToList(lectures.getContent()), lectures.getPageable(), lectures.getTotalPages()); + } + + Lecture mapToLecture(LectureCreateDto dto); +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureRepository.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureRepository.java new file mode 100644 index 00000000..1fd35bce --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureRepository.java @@ -0,0 +1,21 @@ +package org.fuseri.modulelanguageschool.lecture; +import org.fuseri.modulelanguageschool.course.Language; +import org.fuseri.modulelanguageschool.course.ProficiencyLevel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface LectureRepository extends JpaRepository<Lecture, Long> { + + @Query("SELECT l FROM Lecture l WHERE l.course.id = ?1") + List<Lecture> findAllByCourse(Long id); + + @Query("SELECT l FROM Lecture l WHERE l.course.language = ?1") + List<Lecture> findAllByLang(Language language); + + @Query("SELECT l FROM Lecture l WHERE l.course.language = ?1 AND l.course.proficiency = ?2") + List<Lecture> findAllByLangProf(Language language, ProficiencyLevel proficiencyLevel); +} diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureService.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureService.java new file mode 100644 index 00000000..a9e90bcb --- /dev/null +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureService.java @@ -0,0 +1,102 @@ +package org.fuseri.modulelanguageschool.lecture; + +import org.fuseri.modulelanguageschool.common.ResourceNotFoundException; +import org.fuseri.modulelanguageschool.course.Language; +import org.fuseri.modulelanguageschool.course.ProficiencyLevel; +import org.fuseri.modulelanguageschool.user.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +public class LectureService { + + private final LectureRepository lectureRepository; + + @Autowired + public LectureService(LectureRepository lectureRepository) { + this.lectureRepository = lectureRepository; + } + + @Transactional + public Lecture save(Lecture lecture) { + return lectureRepository.save(lecture); + } + + @Transactional(readOnly = true) + public Lecture findById(Long id) { + return lectureRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Lecture with id: " + id + " was not found.")); + } + + @Transactional(readOnly = true) + public List<Lecture> findAllByCourse(Long id) { + return lectureRepository.findAllByCourse(id); + } + + @Transactional + public Lecture update(Long id, Lecture newLecture) { + Optional<Lecture> optionalLecture = lectureRepository.findById(id); + if (optionalLecture.isPresent()) { + Lecture lecture = optionalLecture.get(); + lecture.setFrom(newLecture.getFrom()); + lecture.setTo(newLecture.getTo()); + lecture.setTopic(newLecture.getTopic()); + lecture.setCourse(newLecture.getCourse()); + lecture.setLecturer(newLecture.getLecturer()); + lecture.setStudents(newLecture.getStudents()); + return lectureRepository.save(lecture); + } else { + throw new ResourceNotFoundException("Lecture with id: " + id + " was not found."); + } + } + + @Transactional + public void delete(Long id) { + lectureRepository.deleteById(id); + } + + public List<Lecture> findAll(Language language) { + return lectureRepository.findAllByLang(language); + } + + public List<Lecture> findAll(Language language, ProficiencyLevel proficiencyLevel) { + return lectureRepository.findAllByLangProf(language, proficiencyLevel); + } + + public Lecture enrol(Long id, User student) { + Optional<Lecture> optionalLecture = lectureRepository.findById(id); + if (optionalLecture.isPresent()) { + Lecture lecture = optionalLecture.get(); + lecture.enrolStudent(student); + return lectureRepository.save(lecture); + } else { + throw new ResourceNotFoundException("Lecture with id: " + id + " was not found."); + } + } + + public Lecture expel(Long id, User student) { + Optional<Lecture> optionalLecture = lectureRepository.findById(id); + if (optionalLecture.isPresent()) { + Lecture lecture = optionalLecture.get(); + lecture.expelStudent(student); + return lectureRepository.save(lecture); + } else { + throw new ResourceNotFoundException("Lecture with id: " + id + " was not found."); + } + } + + public Lecture setLecturer(Long id, User lecturer) { + Optional<Lecture> optionalLecture = lectureRepository.findById(id); + if (optionalLecture.isPresent()) { + Lecture lecture = optionalLecture.get(); + lecture.setLecturer(lecturer); + return lectureRepository.save(lecture); + } else { + throw new ResourceNotFoundException("Lecture with id: " + id + " was not found."); + } + } +} diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java index e254fd91..7c287861 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java @@ -38,12 +38,12 @@ public class LectureTest { LocalDateTime.now().plusDays(2), LocalDateTime.now().plusDays(2).plusHours(2), "Learning how to spell deprecated", - 10, "0"); + 10, 0L); private final LectureDto lectureDto = new LectureDto( LocalDateTime.now().plusDays(2), LocalDateTime.now().plusDays(2).plusHours(2), "Learning how to spell deprecated", - 10, "0", "0"); + 10, 0L, 0L); @Test void createLecture() throws Exception { @@ -81,7 +81,7 @@ public class LectureTest { @Test void findLecture() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(lectureController.find(id)).thenReturn(lectureDto); mockMvc.perform(get("/lectures/find/" + id)) .andExpect(status().isOk()) @@ -96,17 +96,17 @@ public class LectureTest { @Test void findLectureWithoutId() throws Exception { - Mockito.when(lectureController.find(ArgumentMatchers.anyString())).thenReturn(lectureDto); + Mockito.when(lectureController.find(ArgumentMatchers.anyLong())).thenReturn(lectureDto); mockMvc.perform(get("/lectures/find/")) .andExpect(status().is4xxClientError()); } @Test void findLecturesByCourse() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(lectureController.findByCourse(id)).thenReturn(new ArrayList<>()); String response = mockMvc.perform(get("/lectures/findByCourse") - .param("courseId", id)) + .param("courseId", String.valueOf(id))) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); @@ -115,14 +115,14 @@ public class LectureTest { @Test void findLecturesByCourseWithoutParameter() throws Exception { - Mockito.when(lectureController.findByCourse(ArgumentMatchers.anyString())).thenReturn(new ArrayList<>()); + Mockito.when(lectureController.findByCourse(ArgumentMatchers.anyLong())).thenReturn(new ArrayList<>()); mockMvc.perform(get("/lectures/findByCourse")) .andExpect(status().is4xxClientError()); } @Test void updateLecture() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(lectureController.update(ArgumentMatchers.eq(id), ArgumentMatchers.isA(LectureCreateDto.class))) .thenReturn(lectureDto); @@ -142,7 +142,7 @@ public class LectureTest { @Test void updateLectureWithoutParameter() throws Exception { Mockito.when(lectureController. - update(ArgumentMatchers.anyString(), ArgumentMatchers.isA(LectureCreateDto.class))) + update(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(LectureCreateDto.class))) .thenReturn(lectureDto); mockMvc.perform(put("/lectures/update")) .andExpect(status().is4xxClientError()); @@ -150,7 +150,7 @@ public class LectureTest { @Test void deleteLecture() throws Exception { - String id = "0"; + Long id = 0L; Mockito.doNothing().when(lectureController).delete(id); mockMvc.perform(delete("/lectures/delete/" + id)) .andExpect(status().isOk()); @@ -158,14 +158,14 @@ public class LectureTest { @Test void deleteCourseWithoutParameter() throws Exception { - Mockito.doNothing().when(lectureController).delete(ArgumentMatchers.anyString()); + Mockito.doNothing().when(lectureController).delete(ArgumentMatchers.anyLong()); mockMvc.perform(delete("/lectures/delete/")) .andExpect(status().is4xxClientError()); } @Test void setLecturerForLecture() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(lectureController.setLecturer(ArgumentMatchers.eq(id), ArgumentMatchers.isA(UserDto.class))) .thenReturn(lectureDto); @@ -186,7 +186,7 @@ public class LectureTest { @Test void setLecturerForLectureWithoutParameters() throws Exception { - Mockito.when(lectureController.setLecturer(ArgumentMatchers.anyString(), + Mockito.when(lectureController.setLecturer(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(UserDto.class))) .thenReturn(lectureDto); mockMvc.perform(patch("/lectures/setLecturer")) @@ -195,7 +195,7 @@ public class LectureTest { @Test void enrolLecture() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(lectureController.enrol(ArgumentMatchers.eq(id), ArgumentMatchers.isA(UserDto.class))) .thenReturn(lectureDto); @@ -216,7 +216,7 @@ public class LectureTest { @Test void enrolCourseWithoutUserParameters() throws Exception { - Mockito.when(lectureController.enrol(ArgumentMatchers.anyString(), + Mockito.when(lectureController.enrol(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(UserDto.class))) .thenReturn(lectureDto); mockMvc.perform(patch("/lectures/enrol")) @@ -225,7 +225,7 @@ public class LectureTest { @Test void expelLecture() throws Exception { - String id = "0"; + Long id = 0L; Mockito.when(lectureController.expel(ArgumentMatchers.eq(id), ArgumentMatchers.isA(UserDto.class))) .thenReturn(lectureDto); @@ -246,7 +246,7 @@ public class LectureTest { @Test void expelCourseWithoutUserParameters() throws Exception { - Mockito.when(lectureController.expel(ArgumentMatchers.anyString(), + Mockito.when(lectureController.expel(ArgumentMatchers.anyLong(), ArgumentMatchers.isA(UserDto.class))) .thenReturn(lectureDto); mockMvc.perform(patch("/lectures/expel")) -- GitLab From c9cb7d2c0981f5970c1a4513b4ccd125108e3356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <xpokorn8@fi.muni.cz> Date: Sun, 9 Apr 2023 11:42:25 +0200 Subject: [PATCH 4/4] Adding Open API annotations, polishing api --- application/module-language-school/pom.xml | 7 ++++++- .../course/CourseController.java | 17 ++++++++++++++--- .../lecture/LectureController.java | 13 ++++++++++++- .../modulelanguageschool/course/CourseTest.java | 6 +++--- .../lecture/LectureTest.java | 6 +++--- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/application/module-language-school/pom.xml b/application/module-language-school/pom.xml index 76585929..d54d174b 100644 --- a/application/module-language-school/pom.xml +++ b/application/module-language-school/pom.xml @@ -63,8 +63,13 @@ <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-annotations</artifactId> + <version>1.6.9</version> + </dependency> - </dependencies> + </dependencies> <build> <plugins> diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java index 6d11099e..353dcd43 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/course/CourseController.java @@ -1,5 +1,7 @@ package org.fuseri.modulelanguageschool.course; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import jakarta.validation.Valid; import org.fuseri.model.dto.course.CourseCreateDto; import org.fuseri.model.dto.course.CourseDto; @@ -12,13 +14,13 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; import java.util.List; /** * This class represents a RESTful controller for Courses resources. * It handles incoming HTTP requests related to courses, and delegates them to the appropriate service method. */ +@Api(tags = "Course Controller") @RestController @RequestMapping("/courses") public class CourseController { @@ -36,7 +38,8 @@ public class CourseController { * @param dto the CourseCreateDto containing the course data * @return the newly created CourseDto */ - @PostMapping("/create") + @ApiOperation(value = "Create a new course") + @PostMapping public CourseDto create(@Valid @RequestBody CourseCreateDto dto) { return courseFacade.create(dto); } @@ -47,9 +50,10 @@ public class CourseController { * @param id the ID of the course to retrieve * @return the CourseDto for the specified ID */ + @ApiOperation(value = "Retrieve a course by ID") @GetMapping("/find/{id}") public CourseDto find(@PathVariable Long id) { - return courseFacade.findById(id); + return courseFacade.findById(id); } /** @@ -58,6 +62,7 @@ public class CourseController { * @param page the page number to retrieve * @return the Result containing the requested page of CourseDtos */ + @ApiOperation(value = "Retrieve a paginated list of courses") @GetMapping("/findAll") public Page<CourseDto> findAll(@RequestParam int page) { return courseFacade.findAll(PageRequest.of(page, 10, Sort.by(Sort.Direction.ASC, "name"))); @@ -69,6 +74,7 @@ public class CourseController { * @param lang the language to find courses of * @return the Result containing the requested page of CourseDtos */ + @ApiOperation(value = "Retrieve a paginated list of courses of a given language") @GetMapping("/findAllByLang") public List<CourseDto> findAll(@RequestParam LanguageTypeDto lang) { return courseFacade.findAll(lang); @@ -81,6 +87,7 @@ public class CourseController { * @param prof the proficiency of the language * @return the Result containing the requested page of CourseDtos */ + @ApiOperation(value = "Retrieve a paginated list of courses of a given language and proficiency") @GetMapping("/findAllByLangProf") public List<CourseDto> findAll(@RequestParam LanguageTypeDto lang, @RequestParam ProficiencyLevelDto prof) { @@ -94,6 +101,7 @@ public class CourseController { * @param dto the CourseCreateDto containing the updated course data * @return the updated CourseDto */ + @ApiOperation(value = "Update an existing course") @PutMapping("/update/{id}") public CourseDto update(@PathVariable Long id, @Valid @RequestBody CourseCreateDto dto) { return courseFacade.update(id, dto); @@ -104,6 +112,7 @@ public class CourseController { * * @param id the ID of the course to delete */ + @ApiOperation(value = "Delete a course by ID") @DeleteMapping("/delete/{id}") public void delete(@PathVariable Long id) { courseFacade.delete(id); @@ -117,6 +126,7 @@ public class CourseController { * @param student UserDto for the student * @return the CourseDto representing the updated course */ + @ApiOperation(value = "Add student to the existing course") @PatchMapping("/enrol/{id}") public CourseDto enrol(@PathVariable Long id, @RequestBody UserDto student) { return courseFacade.enrol(id, student); @@ -129,6 +139,7 @@ public class CourseController { * @param student UserDto for the student * @return the CourseDto representing the updated course */ + @ApiOperation(value = "Remove student from the existing course") @PatchMapping("/expel/{id}") public CourseDto expel(@PathVariable Long id, @RequestBody UserDto student) { return courseFacade.expel(id, student); diff --git a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java index 99da62d6..0922a5e2 100644 --- a/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java +++ b/application/module-language-school/src/main/java/org/fuseri/modulelanguageschool/lecture/LectureController.java @@ -1,5 +1,7 @@ package org.fuseri.modulelanguageschool.lecture; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import jakarta.validation.Valid; import org.fuseri.model.dto.lecture.LectureCreateDto; import org.fuseri.model.dto.lecture.LectureDto; @@ -13,6 +15,7 @@ import java.util.List; * This class represents a RESTful controller for Lecture resources. * It handles incoming HTTP requests related to lectures, and delegates them to the appropriate service method. */ +@Api(tags = "Lecture Controller") @RestController @RequestMapping("/lectures") public class LectureController { @@ -31,7 +34,8 @@ public class LectureController { * @param lecture the LectureDto representing the lecture to be created * @return the LectureDto representing the newly created lecture */ - @PostMapping("/create") + @ApiOperation(value = "Create a new lecture") + @PostMapping public LectureDto create(@Valid @RequestBody LectureCreateDto lecture) { return lectureFacade.create(lecture); } @@ -42,6 +46,7 @@ public class LectureController { * @param courseId the ID of the lecture to find * @return the LectureDto representing the found lecture */ + @ApiOperation(value = "Retrieve a lecture by its ID") @GetMapping("find/{courseId}") public LectureDto find(@PathVariable Long courseId) { return lectureFacade.findById(courseId); @@ -53,6 +58,7 @@ public class LectureController { * @param courseId the course to retrieve lectures from * @return the list of LectureDtos */ + @ApiOperation(value = "Retrieve a list of lectures for the corresponding course") @GetMapping("/findByCourse") public List<LectureDto> findByCourse(@Valid @RequestParam Long courseId) { return lectureFacade.findAll(courseId); @@ -64,6 +70,7 @@ public class LectureController { * @param lecture the CourseCreateDto representing the updated lecture * @return the LectureDto representing the updated lecture */ + @ApiOperation(value = "Update an existing lecture") @PutMapping("/update/{id}") public LectureDto update(@PathVariable Long id, @Valid @RequestBody LectureCreateDto lecture) { return lectureFacade.update(id, lecture); @@ -74,6 +81,7 @@ public class LectureController { * * @param id the ID of the lecture to delete */ + @ApiOperation(value = "Delete a lecture by its ID") @DeleteMapping("/delete/{id}") public void delete(@PathVariable Long id) { lectureFacade.delete(id); @@ -87,6 +95,7 @@ public class LectureController { * @param lecturerDto UserDto for the course lecturer * @return the LectureDto representing the updated lecture */ + @ApiOperation(value = "Add lecturer to the existing lecture") @PatchMapping("/setLecturer/{id}") public LectureDto setLecturer(@PathVariable Long id, @RequestBody UserDto lecturerDto) { return lectureFacade.setLecturer(id, lecturerDto); @@ -99,6 +108,7 @@ public class LectureController { * @param student UserDto for the course student * @return the LectureDto representing the updated lecture */ + @ApiOperation(value = "Add student to the existing lecture") @PatchMapping("/enrol/{id}") public LectureDto enrol(@PathVariable Long id, @RequestBody UserDto student) { return lectureFacade.enrol(id, student); @@ -111,6 +121,7 @@ public class LectureController { * @param student UserDto for the course student * @return the LectureDto representing the updated lecture */ + @ApiOperation(value = "Remove student from the existing lecture") @PatchMapping("/expel/{id}") public LectureDto expel(@PathVariable Long id, @RequestBody UserDto student) { return lectureFacade.expel(id, student); diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java index bab37001..ec5cff89 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/course/CourseTest.java @@ -53,7 +53,7 @@ public class CourseTest { @Test void createCourse() throws Exception { Mockito.when(courseController.create(ArgumentMatchers.isA(CourseCreateDto.class))).thenReturn(courseDto); - mockMvc.perform(post("/courses/create") + mockMvc.perform(post("/courses") .content(asJsonString(courseCreateDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) @@ -70,7 +70,7 @@ public class CourseTest { CourseCreateDto invalidCourseCreateDto = new CourseCreateDto(null, null, null, null); Mockito.when(courseController.create(ArgumentMatchers.isA(CourseCreateDto.class))).thenReturn(courseDto); - mockMvc.perform(post("/courses/create") + mockMvc.perform(post("/courses") .content(asJsonString(invalidCourseCreateDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); @@ -79,7 +79,7 @@ public class CourseTest { @Test void createCourseWithoutParameter() throws Exception { Mockito.when(courseController.create(ArgumentMatchers.isA(CourseCreateDto.class))).thenReturn(courseDto); - mockMvc.perform(post("/courses/create")) + mockMvc.perform(post("/courses")) .andExpect(status().is4xxClientError()); } diff --git a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java index 7c287861..a0d327ac 100644 --- a/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java +++ b/application/module-language-school/src/test/java/org/fuseri/modulelanguageschool/lecture/LectureTest.java @@ -48,7 +48,7 @@ public class LectureTest { @Test void createLecture() throws Exception { Mockito.when(lectureController.create(ArgumentMatchers.isA(LectureCreateDto.class))).thenReturn(lectureDto); - mockMvc.perform(post("/lectures/create") + mockMvc.perform(post("/lectures") .content(asJsonString(lectureCreateDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) @@ -66,7 +66,7 @@ public class LectureTest { LectureCreateDto invalidLectureCreateDto = new LectureCreateDto(null, null, null, null, null); Mockito.when(lectureController.create(ArgumentMatchers.isA(LectureCreateDto.class))).thenReturn(lectureDto); - mockMvc.perform(post("/lectures/create") + mockMvc.perform(post("/lectures") .content(asJsonString(invalidLectureCreateDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); @@ -75,7 +75,7 @@ public class LectureTest { @Test void createLectureWithoutParameter() throws Exception { Mockito.when(lectureController.create(ArgumentMatchers.isA(LectureCreateDto.class))).thenReturn(lectureDto); - mockMvc.perform(post("/lectures/create")) + mockMvc.perform(post("/lectures")) .andExpect(status().is4xxClientError()); } -- GitLab