diff --git a/src/main/java/com/provedcode/ProvedCodeApplication.java b/src/main/java/com/provedcode/ProvedCodeApplication.java index 9267230..fc86627 100644 --- a/src/main/java/com/provedcode/ProvedCodeApplication.java +++ b/src/main/java/com/provedcode/ProvedCodeApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class ProvedCodeApplication { public static void main(String[] args) { diff --git a/src/main/java/com/provedcode/config/PageConfig.java b/src/main/java/com/provedcode/config/PageConfig.java new file mode 100644 index 0000000..ed70939 --- /dev/null +++ b/src/main/java/com/provedcode/config/PageConfig.java @@ -0,0 +1,21 @@ +package com.provedcode.config; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; + +@EnableConfigurationProperties +@PropertySource("pagination.properties") +@ConfigurationProperties(prefix = "page-config") +@Slf4j +public record PageConfig( + int defaultPageNum, + int defaultPageSize +) { + @PostConstruct + void print() { + log.info("page-props = {} ", this); + } +} diff --git a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java new file mode 100644 index 0000000..86300b8 --- /dev/null +++ b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java @@ -0,0 +1,14 @@ +package com.provedcode.handlers; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.server.ResponseStatusException; + +@ControllerAdvice +public class TalentExceptionHandler { + @ExceptionHandler(ResponseStatusException.class) + private ResponseEntity responseStatusExceptionHandler(ResponseStatusException exception) { + return ResponseEntity.status(exception.getStatusCode()).body(exception.getBody()); + } +} diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index 4ef750d..0ca8e61 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -1,35 +1,29 @@ package com.provedcode.talent; -import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.model.entity.TalentSkill; -import com.provedcode.talent.model.response.ShortTalent; -import com.provedcode.talent.repo.TalentRepository; +import com.provedcode.talent.service.TalentService; +import com.provedcode.talent.model.dto.FullTalentDTO; +import com.provedcode.talent.model.dto.ShortTalentDTO; import lombok.AllArgsConstructor; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; -import java.util.stream.Collectors; +import java.util.List; +import java.util.Optional; @RestController @AllArgsConstructor public class TalentController { - TalentRepository talentRepository; + TalentService talentService; - @GetMapping("/api/talent/{id}") - ShortTalent getTalent(@PathVariable("id") long id) { - Talent talent = talentRepository.findById(id) - .orElseThrow( - () -> new UsernameNotFoundException( - "id " + id + " not found")); - return new ShortTalent( - talent.getId(), - talent.getImage(), - talent.getFirstName(), - talent.getLastName(), - talent.getSpecialization(), - talent.getTalentSkills().stream().map(TalentSkill::getSkill).collect(Collectors.toList()) - ); + @GetMapping("/api/talents/{id}") + FullTalentDTO getTalent(@PathVariable("id") long id) { + return talentService.getTalentById(id); + } + + @GetMapping("/api/talents") + @ResponseStatus(HttpStatus.OK) + List getTalents(@RequestParam(value = "page") Optional page, + @RequestParam(value = "size") Optional size) { + return talentService.getTalentsPage(page, size); } } diff --git a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java new file mode 100644 index 0000000..ca767a4 --- /dev/null +++ b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java @@ -0,0 +1,35 @@ +package com.provedcode.talent.mapper; + +import com.provedcode.talent.model.dto.FullTalentDTO; +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.*; + +public interface TalentMapper { + default ShortTalentDTO talentToShortTalentDTO(Talent talent) { + return ShortTalentDTO.builder() + .id(talent.getId()) + .image(talent.getImage()) + .firstname(talent.getFirstName()) + .lastname(talent.getLastName()) + .specialization(talent.getSpecialization()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .build(); + } + + default FullTalentDTO talentToFullTalentDTO(Talent talent) { + return FullTalentDTO.builder() + .id(talent.getId()) + .firstname(talent.getFirstName()) + .lastname(talent.getLastName()) + .bio(talent.getTalentDescription().getBio()) + .additionalInfo(talent.getTalentDescription().getAdditionalInfo()) + .image(talent.getImage()) + .specialization(talent.getSpecialization()) + .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) + .contacts(talent.getTalentContacts().stream().map(TalentContact::getContact).toList()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .attachedFiles(talent.getTalentAttachedFiles().stream().map(TalentAttachedFile::getAttachedFile).toList()) + .build(); + } + +} diff --git a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java new file mode 100644 index 0000000..6dc6a03 --- /dev/null +++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java @@ -0,0 +1,8 @@ +package com.provedcode.talent.mapper.impl; + +import com.provedcode.talent.mapper.TalentMapper; +import org.springframework.stereotype.Component; + +@Component +public class TalentMapperImpl implements TalentMapper { +} diff --git a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java new file mode 100644 index 0000000..a8a72c2 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java @@ -0,0 +1,20 @@ +package com.provedcode.talent.model.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record FullTalentDTO ( + Long id, + String firstname, + String lastname, + String image, + String specialization, + String additionalInfo, + String bio, + List skills, + List links, + List contacts, + List attachedFiles +) {} diff --git a/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java new file mode 100644 index 0000000..a9ca9a7 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java @@ -0,0 +1,16 @@ +package com.provedcode.talent.model.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record ShortTalentDTO( + Long id, + String image, + String firstname, + String lastname, + String specialization, + List skills +) { +} diff --git a/src/main/java/com/provedcode/talent/model/entity/Talent.java b/src/main/java/com/provedcode/talent/model/entity/Talent.java index 232de47..f52f6f1 100644 --- a/src/main/java/com/provedcode/talent/model/entity/Talent.java +++ b/src/main/java/com/provedcode/talent/model/entity/Talent.java @@ -46,4 +46,5 @@ public class Talent { private List talentContacts = new ArrayList<>(); @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) private List talentAttachedFiles = new ArrayList<>(); + } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/TalentRepository.java b/src/main/java/com/provedcode/talent/repo/TalentRepository.java index b2bac22..b2fe0de 100644 --- a/src/main/java/com/provedcode/talent/repo/TalentRepository.java +++ b/src/main/java/com/provedcode/talent/repo/TalentRepository.java @@ -1,7 +1,16 @@ package com.provedcode.talent.repo; import com.provedcode.talent.model.entity.Talent; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; -public interface TalentRepository extends JpaRepository { +import java.util.List; +import java.util.Optional; + +public interface TalentRepository { + + List getTalentsPage(PageRequest page); + + Optional findById(Long aLong); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java new file mode 100644 index 0000000..aaa9688 --- /dev/null +++ b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java @@ -0,0 +1,28 @@ +package com.provedcode.talent.repo.db; + +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.TalentRepository; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +public interface TalentEntityRepository extends + JpaRepository, + TalentRepository { + @Transactional(readOnly = true) + default List getTalents() { + return findAll(); + } + + @Override + @Transactional(readOnly = true) + default List getTalentsPage(PageRequest page) { + return findAll(page).stream().toList(); + } + + @Override + Optional findById(Long aLong); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/service/TalentService.java b/src/main/java/com/provedcode/talent/service/TalentService.java new file mode 100644 index 0000000..3b1147a --- /dev/null +++ b/src/main/java/com/provedcode/talent/service/TalentService.java @@ -0,0 +1,14 @@ +package com.provedcode.talent.service; + +import com.provedcode.talent.model.dto.FullTalentDTO; +import com.provedcode.talent.model.dto.ShortTalentDTO; + +import java.util.List; +import java.util.Optional; + + +public interface TalentService { + List getTalentsPage(Optional page, Optional size); + + FullTalentDTO getTalentById(long id); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java new file mode 100644 index 0000000..5f23955 --- /dev/null +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -0,0 +1,52 @@ +package com.provedcode.talent.service.impl; + +import com.provedcode.config.PageConfig; +import com.provedcode.talent.service.TalentService; +import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.FullTalentDTO; +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.TalentRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Service +@Slf4j +@AllArgsConstructor +public class TalentServiceImpl implements TalentService { + TalentMapper talentMapper; + TalentRepository talentRepository; + PageConfig pageConfig; + + @Override + public List getTalentsPage(Optional page, Optional size) { + if (page.orElse(pageConfig.defaultPageNum()) < 0) { + throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); + } + if (size.orElse(pageConfig.defaultPageSize()) <= 0) { + throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); + } + return talentRepository.getTalentsPage( + PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) + .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) + .toList(); + } + + @Override + public FullTalentDTO getTalentById(long id) { + Optional talent = talentRepository.findById(id); + if (talent.isEmpty()){ + throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); + } + return talentMapper.talentToFullTalentDTO(talent.get()); + } +} diff --git a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java new file mode 100644 index 0000000..84ba719 --- /dev/null +++ b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java @@ -0,0 +1,51 @@ +package com.provedcode.talent.service.mock; + +import com.provedcode.config.PageConfig; +import com.provedcode.talent.service.TalentService; +import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.FullTalentDTO; +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.TalentRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +//@Service +@Slf4j +@AllArgsConstructor +public class TalentServiceMock implements TalentService { + TalentMapper talentMapper; + TalentRepository talentRepository; + PageConfig pageConfig; + + @Override + public List getTalentsPage(Optional page, Optional size) { + if (page.orElse(pageConfig.defaultPageNum()) < 0) { + throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); + } + if (size.orElse(pageConfig.defaultPageSize()) <= 0) { + throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); + } + return talentRepository.getTalentsPage( + PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) + .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) + .toList(); + } + + @Override + public FullTalentDTO getTalentById(long id) { + Optional talent = talentRepository.findById(id); + if (talent.isEmpty()){ + throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); + } + return talentMapper.talentToFullTalentDTO(talent.get()); + } +} \ No newline at end of file diff --git a/src/main/resources/pagination.properties b/src/main/resources/pagination.properties new file mode 100644 index 0000000..7ba1262 --- /dev/null +++ b/src/main/resources/pagination.properties @@ -0,0 +1,3 @@ +## +page-config.default-page-num=0 +page-config.default-page-size=5 \ No newline at end of file