Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
<java.version>17</java.version>
</properties>
<dependencies>

<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/provedcode/ProvedCodeApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

@SpringBootApplication
@ConfigurationPropertiesScan
public class ProvedCodeApplication {
public class ProvedCodeApplication {

public static void main(String[] args) {
SpringApplication.run(ProvedCodeApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(ProvedCodeApplication.class, args);
}

}
3 changes: 2 additions & 1 deletion src/main/java/com/provedcode/config/PageProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
@Slf4j
public record PageProperties(
int defaultPageNum,
int defaultPageSize
int defaultPageSize,
String defaultSortBy
) {
@PostConstruct
void print() {
Expand Down
42 changes: 23 additions & 19 deletions src/main/java/com/provedcode/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,37 @@
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
//http://localhost:8080/swagger-ui/index.html
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(c -> c
.requestMatchers("/actuator/health").permitAll() // for DevOps
.requestMatchers(antMatcher("/h2/**")).permitAll()
.requestMatchers(antMatcher("/api/talents/**")).permitAll()
.requestMatchers(antMatcher("/error")).permitAll()
.requestMatchers(antMatcher("/v3/api-docs/**")).permitAll() // for openAPI
.requestMatchers(antMatcher("/swagger-ui/**")).permitAll() // for openAPI
.requestMatchers(antMatcher("/swagger-ui.html")).permitAll() // for openAPI
.anyRequest().authenticated()
);

http.exceptionHandling(c -> c
.authenticationEntryPoint((request, response, authException) -> {
log.info("Authentication failed {}, message:{}",
describe(request),
authException.getMessage());
response.sendError(
HttpStatus.UNAUTHORIZED.value(),
authException.getMessage());
}
log.info("Authentication failed {}, message:{}",
describe(request),
authException.getMessage());
response.sendError(
HttpStatus.UNAUTHORIZED.value(),
authException.getMessage());
}
)
.accessDeniedHandler((request, response, accessDeniedException) -> {
log.info("Authorization failed {},message: {}",
describe(request),
accessDeniedException.getMessage());
response.sendError(HttpStatus.FORBIDDEN.value(),
accessDeniedException.getMessage());
}
log.info("Authorization failed {},message: {}",
describe(request),
accessDeniedException.getMessage());
response.sendError(HttpStatus.FORBIDDEN.value(),
accessDeniedException.getMessage());
}
)
);

Expand All @@ -82,10 +86,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
http.sessionManagement().sessionCreationPolicy(STATELESS);

http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.exceptionHandling(c -> c
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
.exceptionHandling(c -> c
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);

return http.build();
}
Expand Down Expand Up @@ -135,8 +139,8 @@ UserDetailsService userDetailsService(
UserInfoMapper mapper
) {
return login -> repository.findByLogin(login)
.map(mapper::toUserDetails)
.orElseThrow(() -> new UsernameNotFoundException(login + " not found"));
.map(mapper::toUserDetails)
.orElseThrow(() -> new UsernameNotFoundException(login + " not found"));
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.provedcode.talent.controller;

import com.provedcode.talent.mapper.TalentProofMapper;
import com.provedcode.talent.model.dto.AddProofDTO;
import com.provedcode.talent.model.dto.FullProofDTO;
import com.provedcode.talent.model.dto.ProofDTO;
import com.provedcode.talent.service.TalentProofService;
import com.provedcode.user.model.dto.SessionInfoDTO;
import lombok.AllArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
Expand All @@ -21,8 +27,25 @@ public class TalentProofController {

@GetMapping("/proofs")
Page<ProofDTO> getAllProofs(@RequestParam(value = "page") Optional<Integer> page,
@RequestParam(value = "size") Optional<Integer> size) {
return talentProofService.getAllProofsPage(page, size).map(talentProofMapper::toProofDTO);
@RequestParam(value = "size") Optional<Integer> size,
@RequestParam(value = "order-by") Optional<String> orderBy) {
return talentProofService.getAllProofsPage(page, size, orderBy).map(talentProofMapper::toProofDTO);
}

@PreAuthorize("hasRole('TALENT')")
@DeleteMapping("/{talent-id}/proofs/{proof-id}")
SessionInfoDTO deleteProof(@PathVariable(value = "talent-id") long talentId,
@PathVariable(value = "proof-id") long proofId,
Authentication authentication) {
return talentProofService.deleteProofById(talentId, proofId, authentication);
}

@PreAuthorize("hasRole('TALENT')")
@PostMapping("/{talent-id}/proofs")
ResponseEntity<?> addProof(@PathVariable(value = "talent-id") long talentId,
@RequestBody AddProofDTO addProofDTO,
Authentication authentication) {
return talentProofService.addProof(addProofDTO, talentId, authentication);
}

@GetMapping("/{talent-id}/proofs")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.provedcode.talent.model.dto;

public record AddProofDTO(
String link,
String text
) {
}
116 changes: 94 additions & 22 deletions src/main/java/com/provedcode/talent/service/TalentProofService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,36 @@

import com.provedcode.config.PageProperties;
import com.provedcode.talent.model.ProofStatus;
import com.provedcode.talent.model.dto.AddProofDTO;
import com.provedcode.talent.model.dto.FullProofDTO;
import com.provedcode.talent.model.dto.ProofDTO;
import com.provedcode.talent.model.entity.Talent;
import com.provedcode.talent.model.entity.TalentProof;
import com.provedcode.talent.repo.TalentProofRepository;
import com.provedcode.talent.repo.TalentRepository;
import com.provedcode.user.model.dto.SessionInfoDTO;
import com.provedcode.user.model.entity.UserInfo;
import com.provedcode.user.repo.UserInfoRepository;
import com.provedcode.utill.ValidateTalentForCompliance;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.time.LocalDateTime;
import java.util.Optional;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;

@Service
@AllArgsConstructor
Expand All @@ -32,27 +41,89 @@ public class TalentProofService {
TalentRepository talentRepository;
UserInfoRepository userInfoRepository;
PageProperties pageProperties;
ValidateTalentForCompliance validateTalentForCompliance;

public Page<TalentProof> getAllProofsPage(Optional<Integer> page, Optional<Integer> size) {
public Page<TalentProof> getAllProofsPage(Optional<Integer> page, Optional<Integer> size,
Optional<String> orderBy) {
if (page.orElse(pageProperties.defaultPageNum()) < 0) {
throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0");
}
if (size.orElse(pageProperties.defaultPageSize()) <= 0) {
throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1");
}

if (orderBy.isPresent()) {
if (!orderBy.get().equalsIgnoreCase(Sort.Direction.ASC.name()) &&
!orderBy.get().equalsIgnoreCase(Sort.Direction.DESC.name())) {
throw new ResponseStatusException(BAD_REQUEST, "'orderBy' query parameter must be ASC or DESC");
}
Sort sort =
orderBy.get().equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(pageProperties.defaultSortBy())
.ascending()
: Sort.by(pageProperties.defaultSortBy())
.descending();
return talentProofRepository.findByStatus(ProofStatus.PUBLISHED,
PageRequest.of(page.orElse(
pageProperties.defaultPageNum()),
size.orElse(
pageProperties.defaultPageSize()), sort));
}
return talentProofRepository.findByStatus(ProofStatus.PUBLISHED,
PageRequest.of(page.orElse(
pageProperties.defaultPageNum()),
size.orElse(
pageProperties.defaultPageSize())));
PageRequest.of(page.orElse(
pageProperties.defaultPageNum()),
size.orElse(
pageProperties.defaultPageSize())));
}

@Transactional
public SessionInfoDTO deleteProofById(long talentId, long proofId, Authentication authentication) {

Optional<Talent> talent = talentRepository.findById(talentId);
Optional<TalentProof> talentProof = talentProofRepository.findById(proofId);
Optional<UserInfo> userInfo = userInfoRepository.findByLogin(authentication.getName());
validateTalentForCompliance.userVerification(talent, talentProof, userInfo, talentId, proofId);
talentProofRepository.delete(talentProof.orElseThrow(() -> new ResponseStatusException(NOT_IMPLEMENTED)));
return new SessionInfoDTO("deleted", "null");
}

public ResponseEntity<?> addProof(AddProofDTO addProofDTO, long talentId, Authentication authentication) {

Optional<Talent> talent = talentRepository.findById(talentId);
Optional<UserInfo> userInfo = userInfoRepository.findByLogin(authentication.getName());

validateTalentForCompliance.userVerification(talent, userInfo, talentId);

TalentProof talentProof = TalentProof.builder()
.talent(talent.get())
.talentId(talentId)
.link(addProofDTO.link())
.text(addProofDTO.text())
.status(ProofStatus.DRAFT)
.created(LocalDateTime.now())
.build();

talentProofRepository.save(talentProof);

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(talentProof.getId())
.toUri();

return ResponseEntity.created(location).build();
}

public FullProofDTO getTalentProofs(Long talentId, Optional<Integer> page, Optional<Integer> size,
Optional<String> direction, Authentication authentication, String... sortProperties) {
Optional<String> direction, Authentication authentication,
String... sortProperties) {
Talent talent = talentRepository.findById(talentId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Talent with id = %s not found".formatted(talentId)));
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"Talent with id = %s not found".formatted(
talentId)));
UserInfo userInfo = userInfoRepository.findByLogin(authentication.getName())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Talent with id = %s not found".formatted(talentId)));
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"Talent with id = %s not found".formatted(
talentId)));
Page<TalentProof> proofs;
PageRequest pageRequest;
String sortDirection = direction.orElseGet(Sort.DEFAULT_DIRECTION::name);
Expand All @@ -63,7 +134,8 @@ public FullProofDTO getTalentProofs(Long talentId, Optional<Integer> page, Optio
if (size.orElse(pageProperties.defaultPageSize()) <= 0) {
throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1");
}
if (!sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) && !sortDirection.equalsIgnoreCase(Sort.Direction.DESC.name())) {
if (!sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) &&
!sortDirection.equalsIgnoreCase(Sort.Direction.DESC.name())) {
throw new ResponseStatusException(BAD_REQUEST, "'direction' query param must be equals ASC or DESC");
}

Expand All @@ -84,17 +156,17 @@ public FullProofDTO getTalentProofs(Long talentId, Optional<Integer> page, Optio
}

return FullProofDTO.builder()
.id(talent.getId())
.image(talent.getImage())
.firstName(talent.getFirstName())
.lastName(talent.getLastName())
.specialization(talent.getSpecialization())
.proofs(proofs.map(i -> ProofDTO.builder()
.id(i.getId())
.created(i.getCreated().toString())
.link(i.getLink())
.text(i.getText())
.status(i.getStatus()).build()))
.build();
.id(talent.getId())
.image(talent.getImage())
.firstName(talent.getFirstName())
.lastName(talent.getLastName())
.specialization(talent.getSpecialization())
.proofs(proofs.map(i -> ProofDTO.builder()
.id(i.getId())
.created(i.getCreated().toString())
.link(i.getLink())
.text(i.getText())
.status(i.getStatus()).build()))
.build();
}
}
}
Loading