From 981aa12c2e81b0728010a7b5a7ddaa67b6b3e9e9 Mon Sep 17 00:00:00 2001 From: Denis Boyko Date: Mon, 24 Apr 2023 17:47:27 +0300 Subject: [PATCH 1/5] us3 --- .../kudos/controller/KudosController.java | 40 ++--- .../provedcode/kudos/model/entity/Kudos.java | 2 + .../response/KudosAmountWithSponsor.java | 13 ++ .../kudos/service/KudosService.java | 150 ++++++++++-------- .../sponsor/controller/SponsorController.java | 1 - .../sponsor/model/entity/Sponsor.java | 2 + .../sponsor/service/SponsorService.java | 13 +- .../impl/AuthenticationServiceImpl.java | 85 +++++----- .../db/changelog/changeset/add-data.sql | 33 ++-- .../db/changelog/changeset/create-tables.sql | 2 + 10 files changed, 189 insertions(+), 152 deletions(-) create mode 100644 src/main/java/com/provedcode/kudos/model/response/KudosAmountWithSponsor.java diff --git a/src/main/java/com/provedcode/kudos/controller/KudosController.java b/src/main/java/com/provedcode/kudos/controller/KudosController.java index fcd85c8..72c32c7 100644 --- a/src/main/java/com/provedcode/kudos/controller/KudosController.java +++ b/src/main/java/com/provedcode/kudos/controller/KudosController.java @@ -1,5 +1,6 @@ package com.provedcode.kudos.controller; +import com.provedcode.kudos.model.response.KudosAmountWithSponsor; import com.provedcode.kudos.service.KudosService; import com.provedcode.kudos.model.response.KudosAmount; import lombok.AllArgsConstructor; @@ -11,22 +12,25 @@ @AllArgsConstructor @RequestMapping("/api/v3/talent") public class KudosController { -// KudosService kudosService; -// -// @GetMapping("/proofs/{proof-id}/kudos") -// KudosAmount getKudosProof(@PathVariable("proof-id") long id) { -// return kudosService.getAmountKudosProof(id); -// } -// -// @PreAuthorize("hasRole('TALENT')") -// @PostMapping("/proofs/{proof-id}/kudos") -// void addKudosToProof(@PathVariable("proof-id") long id, Authentication authentication) { -// kudosService.addKudosToProof(id, authentication); -// } -// -// @PreAuthorize("hasRole('TALENT')") -// @DeleteMapping("/proofs/{proof-id}/kudos") -// void deleteKudosFromProof(@PathVariable("proof-id") long id, Authentication authentication) { -// kudosService.deleteKudosFromProof(id, authentication); -// } + KudosService kudosService; + + @PreAuthorize("hasRole('SPONSOR')") + @GetMapping("/sponsors/{sponsor-id}/kudos") + KudosAmount getKudosForSponsor(@PathVariable("sponsor-id") long id, Authentication authentication) { + return kudosService.getKudosForSponsor(id, authentication); + } + + @PreAuthorize("hasRole('SPONSOR')") + @PostMapping("/proofs/{proof-id}/kudos/{amount}") + void addKudosToProof(@PathVariable("proof-id") long id, + @PathVariable("amount") Long amount, + Authentication authentication) { + kudosService.addKudosToProof(id, amount, authentication); + } + + @PreAuthorize("hasRole('TALENT')") + @GetMapping("/proofs/{proof-id}/kudos") + KudosAmountWithSponsor getProofKudos(@PathVariable("proof-id") long id, Authentication authentication) { + return kudosService.getProofKudos(id, authentication); + } } \ No newline at end of file diff --git a/src/main/java/com/provedcode/kudos/model/entity/Kudos.java b/src/main/java/com/provedcode/kudos/model/entity/Kudos.java index 4af769b..8cacb21 100644 --- a/src/main/java/com/provedcode/kudos/model/entity/Kudos.java +++ b/src/main/java/com/provedcode/kudos/model/entity/Kudos.java @@ -17,6 +17,8 @@ public class Kudos { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; + @Column(name = "amount_kudos") + private Long amountKudos; @ManyToOne @JoinColumn(name = "sponsor_id") private Sponsor sponsor; diff --git a/src/main/java/com/provedcode/kudos/model/response/KudosAmountWithSponsor.java b/src/main/java/com/provedcode/kudos/model/response/KudosAmountWithSponsor.java new file mode 100644 index 0000000..16b302b --- /dev/null +++ b/src/main/java/com/provedcode/kudos/model/response/KudosAmountWithSponsor.java @@ -0,0 +1,13 @@ +package com.provedcode.kudos.model.response; + +import com.provedcode.sponsor.model.dto.SponsorDTO; +import lombok.Builder; + +import java.util.Map; + +@Builder +public record KudosAmountWithSponsor( + Long allKudosOnProof, + Map kudosFromSponsor +) { +} diff --git a/src/main/java/com/provedcode/kudos/service/KudosService.java b/src/main/java/com/provedcode/kudos/service/KudosService.java index c98995e..7dd56ee 100644 --- a/src/main/java/com/provedcode/kudos/service/KudosService.java +++ b/src/main/java/com/provedcode/kudos/service/KudosService.java @@ -2,8 +2,12 @@ import com.provedcode.kudos.model.entity.Kudos; import com.provedcode.kudos.model.response.KudosAmount; +import com.provedcode.kudos.model.response.KudosAmountWithSponsor; import com.provedcode.kudos.repository.KudosRepository; -import com.provedcode.talent.model.ProofStatus; +import com.provedcode.sponsor.mapper.SponsorMapper; +import com.provedcode.sponsor.model.dto.SponsorDTO; +import com.provedcode.sponsor.model.entity.Sponsor; +import com.provedcode.sponsor.repository.SponsorRepository; import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.model.entity.TalentProof; import com.provedcode.talent.repo.TalentProofRepository; @@ -11,13 +15,14 @@ import com.provedcode.user.model.entity.UserInfo; import com.provedcode.user.repo.UserInfoRepository; import lombok.AllArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; import static org.springframework.http.HttpStatus.*; @@ -25,68 +30,79 @@ @AllArgsConstructor @Transactional public class KudosService { -// KudosRepository kudosRepository; -// TalentRepository talentRepository; -// TalentProofRepository talentProofRepository; -// UserInfoRepository userInfoRepository; -// -// @Transactional(readOnly = true) -// public KudosAmount getAmountKudosProof(long id) { -// TalentProof talentProof = talentProofRepository.findById(id) -// .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "proof with id = %s not found".formatted(id))); -// -// if (!talentProof.getStatus().equals(ProofStatus.PUBLISHED)) { -// throw new ResponseStatusException(FORBIDDEN); -// } -// -// long count = kudosRepository.countByProof_Id(id); -// return new KudosAmount(count); -// } -// -// public void addKudosToProof(long id, Authentication authentication) { -// UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) -// .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Talent with id = %s not found".formatted(id))); -// -// Talent talent = userInfo.getTalent(); -// -// TalentProof talentProof = talentProofRepository.findById(id) -// .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Proof with id = %s not found".formatted(id))); -// -// if (talent.getId().equals(talentProof.getTalent().getId())) { -// throw new ResponseStatusException(FORBIDDEN, "Talent can’t give `kudos` to himself"); -// } -// if (kudosRepository.existsByTalent(talent)) { -// throw new ResponseStatusException(CONFLICT, "Talent can give only one `kudos` for one proof"); -// } -// if (!talentProof.getStatus().equals(ProofStatus.PUBLISHED)) { -// throw new ResponseStatusException(FORBIDDEN); -// } -// -// kudosRepository.save(Kudos.builder() -// .proof(talentProof) -// .talent(talent) -// .build()); -// } -// -// public void deleteKudosFromProof(long id, Authentication authentication) { -// UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) -// .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Talent with id = %s not found".formatted(id))); -// Talent talent = userInfo.getTalent(); -// -// TalentProof talentProof = talentProofRepository.findById(id) -// .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Proof with id = %s not found".formatted(id))); -// -// if (!talentProof.getStatus().equals(ProofStatus.PUBLISHED)) { -// throw new ResponseStatusException(FORBIDDEN); -// } -// if (!kudosRepository.existsByTalent(talent)) { -// throw new ResponseStatusException(CONFLICT, "kudos don`t exist"); -// } -// -// Kudos kudos = talent.getCudoses().stream().filter(i -> i.getProof().getId().equals(id)).findFirst().orElseThrow(); -// talentProof.getKudos().remove(kudos); -// talent.getCudoses().remove(kudos); -// -// talentRepository.save(talent); -// } + KudosRepository kudosRepository; + TalentProofRepository talentProofRepository; + UserInfoRepository userInfoRepository; + SponsorRepository sponsorRepository; + TalentRepository talentRepository; + SponsorMapper sponsorMapper; + + + public void addKudosToProof(long id, Long amount, Authentication authentication) { + UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, + "Sponsor with id = %s not found".formatted(id))); + Sponsor sponsor = sponsorRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(NOT_FOUND, + String.format("sponsor with id = %d not found", id))); + TalentProof talentProof = talentProofRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Proof with id = %s not found".formatted(id))); + if (sponsor.getAmountKudos() < amount) { + throw new ResponseStatusException(FORBIDDEN, "The sponsor cannot give more kudos than he has"); + } + sponsor.setAmountKudos(sponsor.getAmountKudos() - amount); + kudosRepository.save(Kudos.builder() + .amountKudos(amount) + .proof(talentProof) + .sponsor(sponsor) + .build()); + } + + @Transactional(readOnly = true) + public KudosAmount getKudosForSponsor(long id, Authentication authentication) { + UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, + "Sponsor with id = %s not found".formatted(id))); + Sponsor sponsor = sponsorRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(NOT_FOUND, + String.format("sponsor with id = %d not found", id))); + if (sponsor.getId() != userInfo.getSponsor().getId()) { + throw new ResponseStatusException(FORBIDDEN); + } + return new KudosAmount(sponsor.getAmountKudos()); + } + + @Transactional(readOnly = true) + public KudosAmountWithSponsor getProofKudos(long id, Authentication authentication) { + UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, + "Sponsor with id = %s not found".formatted(id))); + Talent talent = talentRepository.findById(userInfo.getId()) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, + "Talent with id = %s not found".formatted(id))); + TalentProof talentProof = talentProofRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, + "Proof with id = %s not found".formatted(id))); + + if (talent.getId() == talentProof.getTalent().getId()) { + Map kudosFromSponsor = talentProof.getKudos().stream() + .collect(Collectors.toMap( + sponsor -> sponsorMapper.toDto(sponsor.getSponsor()), + Kudos::getAmountKudos, + (sponsor, kudos) -> sponsor, + HashMap::new + )); + Long counter = talentProof.getKudos().stream().map(i -> i.getAmountKudos()) + .mapToLong(Long::intValue).sum(); + return KudosAmountWithSponsor.builder() + .allKudosOnProof(counter) + .kudosFromSponsor(kudosFromSponsor).build(); + } else { + Long counter = talentProof.getKudos().stream().map(i -> i.getAmountKudos()) + .mapToLong(Long::intValue).sum(); + return KudosAmountWithSponsor.builder() + .allKudosOnProof(counter) + .kudosFromSponsor(null).build(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/provedcode/sponsor/controller/SponsorController.java b/src/main/java/com/provedcode/sponsor/controller/SponsorController.java index 4e94b9d..0d7081c 100644 --- a/src/main/java/com/provedcode/sponsor/controller/SponsorController.java +++ b/src/main/java/com/provedcode/sponsor/controller/SponsorController.java @@ -4,7 +4,6 @@ import com.provedcode.sponsor.model.dto.SponsorDTO; import com.provedcode.sponsor.model.dto.SponsorEditDTO; import com.provedcode.sponsor.service.SponsorService; -import com.provedcode.talent.model.dto.FullTalentDTO; import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/provedcode/sponsor/model/entity/Sponsor.java b/src/main/java/com/provedcode/sponsor/model/entity/Sponsor.java index 35ea8a0..30b12d7 100644 --- a/src/main/java/com/provedcode/sponsor/model/entity/Sponsor.java +++ b/src/main/java/com/provedcode/sponsor/model/entity/Sponsor.java @@ -21,6 +21,8 @@ public class Sponsor { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; + @Column(name = "amount_kudos") + private Long amountKudos; @NotEmpty @Column(name = "first_name", length = 20) private String firstName; diff --git a/src/main/java/com/provedcode/sponsor/service/SponsorService.java b/src/main/java/com/provedcode/sponsor/service/SponsorService.java index c1cc416..933fb69 100644 --- a/src/main/java/com/provedcode/sponsor/service/SponsorService.java +++ b/src/main/java/com/provedcode/sponsor/service/SponsorService.java @@ -2,8 +2,6 @@ import com.provedcode.config.PageProperties; import com.provedcode.kudos.model.entity.Kudos; -import com.provedcode.kudos.repository.KudosRepository; -import com.provedcode.sponsor.model.dto.SponsorDTO; import com.provedcode.sponsor.model.dto.SponsorEditDTO; import com.provedcode.sponsor.model.entity.Sponsor; import com.provedcode.sponsor.repository.SponsorRepository; @@ -19,8 +17,6 @@ import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import static org.springframework.http.HttpStatus.*; @@ -31,7 +27,6 @@ public class SponsorService { PageProperties pageProperties; SponsorRepository sponsorRepository; UserInfoRepository userInfoRepository; - private final KudosRepository kudosRepository; @Transactional(readOnly = true) public Page getAllSponsors(Optional page, Optional size) { @@ -77,13 +72,7 @@ public Sponsor editSponsorById(long id, SponsorEditDTO sponsorEditDTO, Authentic } if (sponsorEditDTO.countOfKudos() != null) { if (sponsorEditDTO.countOfKudos() > 0) { - List kudosList = IntStream.iterate(0, i -> i < sponsorEditDTO.countOfKudos(), i -> i + 1).boxed() - .map(i -> Kudos.builder() - .sponsor(sponsor) - .build()) - .toList(); - kudosRepository.saveAll(kudosList); - sponsor.getKudoses().addAll(kudosList); + sponsor.setAmountKudos(sponsor.getAmountKudos() + sponsorEditDTO.countOfKudos()); } else { throw new ResponseStatusException(BAD_REQUEST, "count of kudos must be greater than 0"); } diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java index ca72df0..9fbd2b2 100644 --- a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java @@ -48,38 +48,38 @@ public class AuthenticationServiceImpl implements AuthenticationService { @Transactional(readOnly = true) public UserInfoDTO login(String name, Collection authorities) { UserInfo userInfo = userInfoRepository.findByLogin(name) - .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, String.format( - "user with login = %s not found", name))); + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, String.format( + "user with login = %s not found", name))); Role userRole = userInfo.getAuthorities().stream().findFirst().orElseThrow().getAuthority(); return UserInfoDTO.builder() - .token(generateJWTToken(name, authorities)) - .id(userRole.equals(Role.TALENT) ? userInfo.getTalent().getId() - : userInfo.getSponsor().getId()) - .role(userRole.name()) - .build(); + .token(generateJWTToken(name, authorities)) + .id(userRole.equals(Role.TALENT) ? userInfo.getTalent().getId() + : userInfo.getSponsor().getId()) + .role(userRole.name()) + .build(); } public UserInfoDTO register(TalentRegistrationDTO user) { if (userInfoRepository.existsByLogin(user.login())) { throw new ResponseStatusException(HttpStatus.CONFLICT, - String.format("user with login = {%s} already exists", user.login())); + String.format("user with login = {%s} already exists", user.login())); } Talent talent = Talent.builder() - .firstName(user.firstName()) - .lastName(user.lastName()) - .specialization(user.specialization()) - .build(); + .firstName(user.firstName()) + .lastName(user.lastName()) + .specialization(user.specialization()) + .build(); talentRepository.save(talent); UserInfo userInfo = UserInfo.builder() - .talent(talent) - .login(user.login()) - .password(passwordEncoder.encode(user.password())) - .authorities(Set.of(authorityRepository.findByAuthority(Role.TALENT).orElseThrow())) - .build(); + .talent(talent) + .login(user.login()) + .password(passwordEncoder.encode(user.password())) + .authorities(Set.of(authorityRepository.findByAuthority(Role.TALENT).orElseThrow())) + .build(); userInfoRepository.save(userInfo); String userLogin = userInfo.getLogin(); @@ -89,31 +89,32 @@ public UserInfoDTO register(TalentRegistrationDTO user) { log.info("user with login {%s} was saved, his authorities: %s".formatted(userLogin, userAuthorities)); return UserInfoDTO.builder() - .token(generateJWTToken(userLogin, userAuthorities)) - .id(talent.getId()) - .role(Role.TALENT.name()) - .build(); + .token(generateJWTToken(userLogin, userAuthorities)) + .id(talent.getId()) + .role(Role.TALENT.name()) + .build(); } public UserInfoDTO register(SponsorRegistrationDTO user) { if (userInfoRepository.existsByLogin(user.login())) { throw new ResponseStatusException(HttpStatus.CONFLICT, - String.format("user with login = {%s} already exists", user.login())); + String.format("user with login = {%s} already exists", user.login())); } Sponsor sponsor = Sponsor.builder() - .firstName(user.firstName()) - .lastName(user.lastName()) - .build(); + .firstName(user.firstName()) + .amountKudos(0L) + .lastName(user.lastName()) + .build(); sponsorRepository.save(sponsor); UserInfo userInfo = UserInfo.builder() - .sponsor(sponsor) - .login(user.login()) - .password(passwordEncoder.encode(user.password())) - .authorities( - Set.of(authorityRepository.findByAuthority(Role.SPONSOR).orElseThrow())) - .build(); + .sponsor(sponsor) + .login(user.login()) + .password(passwordEncoder.encode(user.password())) + .authorities( + Set.of(authorityRepository.findByAuthority(Role.SPONSOR).orElseThrow())) + .build(); userInfoRepository.save(userInfo); String userLogin = userInfo.getLogin(); @@ -123,10 +124,10 @@ public UserInfoDTO register(SponsorRegistrationDTO user) { log.info("user with login {%s} was saved, his authorities: %s".formatted(userLogin, userAuthorities)); return UserInfoDTO.builder() - .token(generateJWTToken(userLogin, userAuthorities)) - .id(sponsor.getId()) - .role(Role.SPONSOR.name()) - .build(); + .token(generateJWTToken(userLogin, userAuthorities)) + .id(sponsor.getId()) + .role(Role.SPONSOR.name()) + .build(); } private String generateJWTToken(String name, Collection authorities) { @@ -134,13 +135,13 @@ private String generateJWTToken(String name, Collection Date: Mon, 24 Apr 2023 18:04:11 +0300 Subject: [PATCH 2/5] us3 bug fix --- .../com/provedcode/kudos/service/KudosService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/provedcode/kudos/service/KudosService.java b/src/main/java/com/provedcode/kudos/service/KudosService.java index 7dd56ee..ed4175d 100644 --- a/src/main/java/com/provedcode/kudos/service/KudosService.java +++ b/src/main/java/com/provedcode/kudos/service/KudosService.java @@ -42,7 +42,7 @@ public void addKudosToProof(long id, Long amount, Authentication authentication) UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Sponsor with id = %s not found".formatted(id))); - Sponsor sponsor = sponsorRepository.findById(id).orElseThrow( + Sponsor sponsor = sponsorRepository.findById(userInfo.getSponsor().getId()).orElseThrow( () -> new ResponseStatusException(NOT_FOUND, String.format("sponsor with id = %d not found", id))); TalentProof talentProof = talentProofRepository.findById(id) @@ -50,6 +50,9 @@ public void addKudosToProof(long id, Long amount, Authentication authentication) if (sponsor.getAmountKudos() < amount) { throw new ResponseStatusException(FORBIDDEN, "The sponsor cannot give more kudos than he has"); } + if (amount <= 0) { + throw new ResponseStatusException(BAD_REQUEST, "amount of kudos must be greater than 0"); + } sponsor.setAmountKudos(sponsor.getAmountKudos() - amount); kudosRepository.save(Kudos.builder() .amountKudos(amount) @@ -63,7 +66,7 @@ public KudosAmount getKudosForSponsor(long id, Authentication authentication) { UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName()) .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Sponsor with id = %s not found".formatted(id))); - Sponsor sponsor = sponsorRepository.findById(id).orElseThrow( + Sponsor sponsor = sponsorRepository.findById(userInfo.getSponsor().getId()).orElseThrow( () -> new ResponseStatusException(NOT_FOUND, String.format("sponsor with id = %d not found", id))); if (sponsor.getId() != userInfo.getSponsor().getId()) { @@ -96,7 +99,8 @@ public KudosAmountWithSponsor getProofKudos(long id, Authentication authenticati .mapToLong(Long::intValue).sum(); return KudosAmountWithSponsor.builder() .allKudosOnProof(counter) - .kudosFromSponsor(kudosFromSponsor).build(); + .kudosFromSponsor(kudosFromSponsor) + .build(); } else { Long counter = talentProof.getKudos().stream().map(i -> i.getAmountKudos()) .mapToLong(Long::intValue).sum(); From 78f8dd76ba3885c1ad1cb4258b17bb58ae4e5d6f Mon Sep 17 00:00:00 2001 From: Denis Boyko Date: Mon, 24 Apr 2023 18:50:10 +0300 Subject: [PATCH 3/5] bug fix --- .../java/com/provedcode/kudos/controller/KudosController.java | 2 +- src/main/java/com/provedcode/kudos/service/KudosService.java | 2 +- .../com/provedcode/sponsor/controller/SponsorController.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/provedcode/kudos/controller/KudosController.java b/src/main/java/com/provedcode/kudos/controller/KudosController.java index 72c32c7..93a10bc 100644 --- a/src/main/java/com/provedcode/kudos/controller/KudosController.java +++ b/src/main/java/com/provedcode/kudos/controller/KudosController.java @@ -10,7 +10,7 @@ @RestController @AllArgsConstructor -@RequestMapping("/api/v3/talent") +@RequestMapping("/api/v3/") public class KudosController { KudosService kudosService; diff --git a/src/main/java/com/provedcode/kudos/service/KudosService.java b/src/main/java/com/provedcode/kudos/service/KudosService.java index ed4175d..ea8615c 100644 --- a/src/main/java/com/provedcode/kudos/service/KudosService.java +++ b/src/main/java/com/provedcode/kudos/service/KudosService.java @@ -70,7 +70,7 @@ public KudosAmount getKudosForSponsor(long id, Authentication authentication) { () -> new ResponseStatusException(NOT_FOUND, String.format("sponsor with id = %d not found", id))); if (sponsor.getId() != userInfo.getSponsor().getId()) { - throw new ResponseStatusException(FORBIDDEN); + throw new ResponseStatusException(FORBIDDEN, "Only the account owner can view the number of kudos"); } return new KudosAmount(sponsor.getAmountKudos()); } diff --git a/src/main/java/com/provedcode/sponsor/controller/SponsorController.java b/src/main/java/com/provedcode/sponsor/controller/SponsorController.java index 0d7081c..2fc2756 100644 --- a/src/main/java/com/provedcode/sponsor/controller/SponsorController.java +++ b/src/main/java/com/provedcode/sponsor/controller/SponsorController.java @@ -46,5 +46,4 @@ SponsorDTO editSponsor(@PathVariable("id") long id, void deleteSponsor(@PathVariable("id") long id, Authentication authentication) { sponsorService.deleteSponsor(id, authentication); } - } \ No newline at end of file From 886376e49cb99223170454f5d9fbf74c62d0ee47 Mon Sep 17 00:00:00 2001 From: Denis Boyko Date: Mon, 24 Apr 2023 19:20:14 +0300 Subject: [PATCH 4/5] add swagger kudos and sponsor --- .../kudos/controller/KudosController.java | 71 +++++++++++++++ .../kudos/service/KudosService.java | 2 +- .../sponsor/controller/SponsorController.java | 90 +++++++++++++++++++ .../talent/controller/TalentController.java | 5 +- .../controller/AuthenticationController.java | 14 +++ 5 files changed, 178 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/provedcode/kudos/controller/KudosController.java b/src/main/java/com/provedcode/kudos/controller/KudosController.java index 93a10bc..20f8bce 100644 --- a/src/main/java/com/provedcode/kudos/controller/KudosController.java +++ b/src/main/java/com/provedcode/kudos/controller/KudosController.java @@ -3,7 +3,13 @@ import com.provedcode.kudos.model.response.KudosAmountWithSponsor; import com.provedcode.kudos.service.KudosService; import com.provedcode.kudos.model.response.KudosAmount; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.AllArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @@ -14,12 +20,55 @@ public class KudosController { KudosService kudosService; + @Operation(summary = "Get all available kudos from a sponsor") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = KudosAmount.class))), + @ApiResponse(responseCode = "404", + description = "NOT FOUND", + content = @Content), + @ApiResponse( + responseCode = "401", + description = "UNAUTHORIZED", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "FORBIDDEN (if not the owner wants to see other sponsor kudos)", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "BAD REQUEST", + content = @Content) + }) @PreAuthorize("hasRole('SPONSOR')") @GetMapping("/sponsors/{sponsor-id}/kudos") KudosAmount getKudosForSponsor(@PathVariable("sponsor-id") long id, Authentication authentication) { return kudosService.getKudosForSponsor(id, authentication); } + @Operation(summary = "As a sponsor I want to estimate talent proof by giving kudos") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content), + @ApiResponse(responseCode = "404", + description = "NOT FOUND", + content = @Content), + @ApiResponse( + responseCode = "401", + description = "UNAUTHORIZED", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "FORBIDDEN (if sponsor does not have enough kudos)", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "BAD REQUEST", + content = @Content) + }) @PreAuthorize("hasRole('SPONSOR')") @PostMapping("/proofs/{proof-id}/kudos/{amount}") void addKudosToProof(@PathVariable("proof-id") long id, @@ -28,6 +77,28 @@ void addKudosToProof(@PathVariable("proof-id") long id, kudosService.addKudosToProof(id, amount, authentication); } + @Operation(summary = "Amount of “kudos” given by sponsors and who gave the “kudos” on proof") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = KudosAmountWithSponsor.class))), + @ApiResponse(responseCode = "404", + description = "NOT FOUND", + content = @Content), + @ApiResponse( + responseCode = "401", + description = "UNAUTHORIZED", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "FORBIDDEN (if not the talent wants to see)", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "BAD REQUEST", + content = @Content) + }) @PreAuthorize("hasRole('TALENT')") @GetMapping("/proofs/{proof-id}/kudos") KudosAmountWithSponsor getProofKudos(@PathVariable("proof-id") long id, Authentication authentication) { diff --git a/src/main/java/com/provedcode/kudos/service/KudosService.java b/src/main/java/com/provedcode/kudos/service/KudosService.java index ea8615c..65dd7bf 100644 --- a/src/main/java/com/provedcode/kudos/service/KudosService.java +++ b/src/main/java/com/provedcode/kudos/service/KudosService.java @@ -68,7 +68,7 @@ public KudosAmount getKudosForSponsor(long id, Authentication authentication) { "Sponsor with id = %s not found".formatted(id))); Sponsor sponsor = sponsorRepository.findById(userInfo.getSponsor().getId()).orElseThrow( () -> new ResponseStatusException(NOT_FOUND, - String.format("sponsor with id = %d not found", id))); + String.format("Sponsor with id = %d not found", id))); if (sponsor.getId() != userInfo.getSponsor().getId()) { throw new ResponseStatusException(FORBIDDEN, "Only the account owner can view the number of kudos"); } diff --git a/src/main/java/com/provedcode/sponsor/controller/SponsorController.java b/src/main/java/com/provedcode/sponsor/controller/SponsorController.java index 2fc2756..ea61f10 100644 --- a/src/main/java/com/provedcode/sponsor/controller/SponsorController.java +++ b/src/main/java/com/provedcode/sponsor/controller/SponsorController.java @@ -4,9 +4,15 @@ import com.provedcode.sponsor.model.dto.SponsorDTO; import com.provedcode.sponsor.model.dto.SponsorEditDTO; import com.provedcode.sponsor.service.SponsorService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @@ -20,6 +26,17 @@ public class SponsorController { SponsorService sponsorService; SponsorMapper sponsorMapper; + @Operation(summary = "Get all sponsors (SponsorDTO)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = Page.class, subTypes = {SponsorDTO.class}))), + @ApiResponse( + responseCode = "400", + description = "BAD_REQUEST (parameter: page or size are incorrect)", + content = @Content) + }) @GetMapping("/sponsors") @ResponseStatus(HttpStatus.OK) Page getSponsors(@RequestParam(value = "page") Optional page, @@ -27,12 +44,59 @@ Page getSponsors(@RequestParam(value = "page") Optional pag return sponsorService.getAllSponsors(page, size).map(sponsorMapper::toDto); } + @Operation(summary = "Get sponsor", + description = "As a sponsor I want to have an opportunity to see my own profile with information") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = SponsorDTO.class))), + @ApiResponse(responseCode = "404", + description = "NOT FOUND", + content = @Content), + @ApiResponse( + responseCode = "401", + description = "UNAUTHORIZED", + content = @Content), + @ApiResponse( + responseCode = "501", + description = "NOT_IMPLEMENTED (login is not valid)", + content = @Content) + }) @PreAuthorize("hasRole('SPONSOR')") @GetMapping("/sponsors/{id}") SponsorDTO getSponsor(@PathVariable("id") long id, Authentication authentication) { return sponsorMapper.toDto(sponsorService.getSponsorById(id, authentication)); } + @Operation(summary = "Edit information about sponsor", + description = "As a sponsor I want to have an opportunity to edit my personal profile by adding new information, " + + "changing already existing information.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = SponsorDTO.class))), + @ApiResponse(responseCode = "404", + description = "NOT FOUND", + content = @Content), + @ApiResponse( + responseCode = "401", + description = "UNAUTHORIZED", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "FORBIDDEN (if not the owner wants to change the sponsor)", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "BAD REQUEST", + content = @Content), + @ApiResponse( + responseCode = "501", + description = "NOT_IMPLEMENTED (login is not valid)", + content = @Content) + }) @PreAuthorize("hasRole('SPONSOR')") @PatchMapping("/sponsors/{id}") SponsorDTO editSponsor(@PathVariable("id") long id, @@ -41,6 +105,32 @@ SponsorDTO editSponsor(@PathVariable("id") long id, return sponsorMapper.toDto(sponsorService.editSponsorById(id, sponsorEditDTO, authentication)); } + @Operation(summary = "Delete sponsor", + description = "As a sponsor I want to have an opportunity to delete personal accounts.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "SUCCESS", + content = @Content), + @ApiResponse(responseCode = "404", + description = "NOT FOUND ", + content = @Content), + @ApiResponse( + responseCode = "401", + description = "UNAUTHORIZED", + content = @Content), + @ApiResponse( + responseCode = "403", + description = "FORBIDDEN (if not the owner wants to delete the talent)", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "BAD REQUEST (incorrect id)", + content = @Content), + @ApiResponse( + responseCode = "501", + description = "NOT_IMPLEMENTED (login is not valid)", + content = @Content), + }) @PreAuthorize("hasRole('SPONSOR')") @DeleteMapping("/sponsors/{id}") void deleteSponsor(@PathVariable("id") long id, Authentication authentication) { diff --git a/src/main/java/com/provedcode/talent/controller/TalentController.java b/src/main/java/com/provedcode/talent/controller/TalentController.java index 4c8ea81..5ab3d23 100644 --- a/src/main/java/com/provedcode/talent/controller/TalentController.java +++ b/src/main/java/com/provedcode/talent/controller/TalentController.java @@ -33,7 +33,7 @@ public class TalentController { TalentService talentService; TalentMapper talentMapper; - @Operation(summary = "Get all talents", + @Operation(summary = "Get all talents (ShortTalentDTO)", description = "As a guest I want to see a page with a list of all “talents” cards displayed with a short description about them") @ApiResponses(value = { @ApiResponse(responseCode = "200", @@ -114,8 +114,7 @@ FullTalentDTO editTalent(@PathVariable("talent-id") long id, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "SUCCESS", - content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = @Schema(implementation = FullTalentDTO.class))), + content = @Content), @ApiResponse(responseCode = "404", description = "NOT FOUND ", content = @Content), diff --git a/src/main/java/com/provedcode/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java index b31e4c2..69559c6 100644 --- a/src/main/java/com/provedcode/user/controller/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -67,6 +67,20 @@ UserInfoDTO register(@RequestBody @Valid TalentRegistrationDTO user) { return authenticationService.register(user); } + @Operation(summary = "Sponsor Registration") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", + description = "CREATED", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = UserInfoDTO.class))), + @ApiResponse(responseCode = "409", + description = "CONFLICT (user with login already exists)", + content = @Content), + @ApiResponse( + responseCode = "400", + description = "BAD_REQUEST", + content = @Content), + }) @PostMapping("/v3/sponsors/register") @ResponseStatus(HttpStatus.CREATED) UserInfoDTO register(@RequestBody @Valid SponsorRegistrationDTO user) { From 7a1a7213d50b05465832552618bab4210d95b9d7 Mon Sep 17 00:00:00 2001 From: Denis Boyko Date: Mon, 24 Apr 2023 19:23:51 +0300 Subject: [PATCH 5/5] add ExceptionHandler --- .../com/provedcode/handlers/ApiError.java | 28 ++++++ .../handlers/TalentExceptionHandler.java | 90 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 src/main/java/com/provedcode/handlers/ApiError.java diff --git a/src/main/java/com/provedcode/handlers/ApiError.java b/src/main/java/com/provedcode/handlers/ApiError.java new file mode 100644 index 0000000..5326437 --- /dev/null +++ b/src/main/java/com/provedcode/handlers/ApiError.java @@ -0,0 +1,28 @@ +package com.provedcode.handlers; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import java.util.Arrays; +import java.util.List; + +@Getter +public class ApiError { + private HttpStatus status; + private String message; + private List errors; + + public ApiError(HttpStatus status, String message, List errors) { + super(); + this.status = status; + this.message = message; + this.errors = errors; + } + + public ApiError(HttpStatus status, String message, String error) { + super(); + this.status = status; + this.message = message; + errors = Arrays.asList(error); + } +} diff --git a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java index 1c63234..cf460c5 100644 --- a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java +++ b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java @@ -1,14 +1,104 @@ package com.provedcode.handlers; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; import org.springframework.web.server.ResponseStatusException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + @ControllerAdvice public class TalentExceptionHandler { @ExceptionHandler(ResponseStatusException.class) private ResponseEntity responseStatusExceptionHandler(ResponseStatusException exception) { return ResponseEntity.status(exception.getStatusCode()).body(exception.getBody()); } + + @ExceptionHandler({SQLException.class}) + public void printSQLException(SQLException ex) { + for (Throwable e : ex) { + if (e instanceof SQLException) { + if (ignoreSQLException(((SQLException) e).getSQLState()) == false) { + + e.printStackTrace(System.err); + System.err.println("SQLState: " + + ((SQLException) e).getSQLState()); + + System.err.println("Error Code: " + + ((SQLException) e).getErrorCode()); + + System.err.println("Message: " + e.getMessage()); + + Throwable t = ex.getCause(); + while (t != null) { + System.out.println("Cause: " + t); + t = t.getCause(); + } + } + } + } + } + + public static boolean ignoreSQLException(String sqlState) { + + if (sqlState == null) { + System.out.println("The SQL state is not defined!"); + return false; + } + + // X0Y32: Jar file already exists in schema + if (sqlState.equalsIgnoreCase("X0Y32")) + return true; + + // 42Y55: Table already exists in schema + if (sqlState.equalsIgnoreCase("42Y55")) + return true; + + return false; + } + + @ExceptionHandler({ Exception.class }) + public ResponseEntity handleAll(Exception ex, WebRequest request) { + ApiError apiError = new ApiError( + HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred"); + return new ResponseEntity( + apiError, new HttpHeaders(), apiError.getStatus()); + } + +//MethodArgumentNotValidException – This exception is thrown +// when an argument annotated with @Valid failed validation: +// @ResponseStatus(HttpStatus.BAD_REQUEST) +// @ExceptionHandler(MethodArgumentNotValidException.class) +// public Map handleValidationExceptions( +// MethodArgumentNotValidException ex) { +// Map errors = new HashMap<>(); +// ex.getBindingResult().getAllErrors().forEach((error) -> { +// String fieldName = ((FieldError) error).getField(); +// String errorMessage = error.getDefaultMessage(); +// errors.put(fieldName, errorMessage); +// }); +// return errors; +// } + + @ExceptionHandler({ ConstraintViolationException.class }) + public ResponseEntity handleConstraintViolation( + ConstraintViolationException ex, WebRequest request) { + List errors = new ArrayList<>(); + for (ConstraintViolation violation : ex.getConstraintViolations()) { + errors.add(violation.getRootBeanClass().getName() + " " + + violation.getPropertyPath() + ": " + violation.getMessage()); + } + + ApiError apiError = + new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors); + return new ResponseEntity( + apiError, new HttpHeaders(), apiError.getStatus()); + } } \ No newline at end of file