diff --git a/pom.xml b/pom.xml
index 9cff8df..56549a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
org.springframework.boot
- spring-boot-starter-oauth2-client
+ spring-boot-starter-oauth2-resource-server
org.springframework.boot
diff --git a/src/main/java/com/provedcode/config/InitConfig.java b/src/main/java/com/provedcode/config/InitConfig.java
new file mode 100644
index 0000000..fe066d2
--- /dev/null
+++ b/src/main/java/com/provedcode/config/InitConfig.java
@@ -0,0 +1,29 @@
+package com.provedcode.config;
+
+import com.provedcode.user.mapper.UserInfoMapper;
+import com.provedcode.user.repo.UserInfoRepository;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class InitConfig implements CommandLineRunner {
+ UserInfoRepository userInfoRepository;
+ PasswordEncoder passwordEncoder;
+ UserInfoMapper userInfoMapper;
+
+ @Override
+ public void run(String... args) throws Exception {
+ // TODO: Method to change passwords for already created users from data.sql
+ userInfoRepository.saveAll(
+ userInfoRepository.findAll().stream()
+ .map(i -> {
+ i.setPassword(passwordEncoder.encode(i.getPassword()));
+ return i;
+ }).toList());
+ }
+}
diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java
index 819c2af..6591a05 100644
--- a/src/main/java/com/provedcode/config/SecurityConfig.java
+++ b/src/main/java/com/provedcode/config/SecurityConfig.java
@@ -1,12 +1,36 @@
package com.provedcode.config;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.provedcode.user.mapper.UserInfoMapper;
+import com.provedcode.user.repo.UserInfoRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
+import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
@@ -19,11 +43,65 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
http.authorizeHttpRequests(c -> c
.requestMatchers("/actuator/health").permitAll() // for DevOps
.requestMatchers(antMatcher("/h2/**")).permitAll()
- .requestMatchers(antMatcher("/api/**")).permitAll()
- .anyRequest().denyAll()
+ .requestMatchers(antMatcher("/api/talents/**")).permitAll()
+ .anyRequest().authenticated()
);
- http.csrf().disable().headers().frameOptions().disable();
+
+ http.httpBasic(Customizer.withDefaults());
+ http.csrf().disable().headers().disable();
+
http.sessionManagement().sessionCreationPolicy(STATELESS);
+
+ http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
+ .exceptionHandling(c -> c
+ .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
+ .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
+ );
+
return http.build();
}
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public KeyPair keyPair() throws NoSuchAlgorithmException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ return keyPairGenerator.generateKeyPair();
+ }
+
+ @Bean
+ public JwtAuthenticationConverter jwtAuthenticationConverter() {
+ JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+ grantedAuthoritiesConverter.setAuthorityPrefix("");
+
+ JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
+ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
+ return jwtAuthenticationConverter;
+ }
+
+ @Bean
+ UserDetailsService userDetailsService(
+ UserInfoRepository repository,
+ UserInfoMapper mapper
+ ) {
+ return login -> repository.findByLogin(login)
+ .map(mapper::toUserDetails)
+ .orElseThrow(() -> new UsernameNotFoundException(login + " not found"));
+ }
+
+ @Bean
+ JwtDecoder jwtDecoder(KeyPair keyPair) {
+ return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();
+ }
+
+ @Bean
+ JwtEncoder jwtEncoder(KeyPair keyPair) {
+ var jwk = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()).privateKey(keyPair.getPrivate()).build();
+ var jwkSet = new ImmutableJWKSet<>(new JWKSet(jwk));
+ return new NimbusJwtEncoder(jwkSet);
+ }
}
diff --git a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java
index 86300b8..575e4a5 100644
--- a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java
+++ b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java
@@ -8,7 +8,7 @@
@ControllerAdvice
public class TalentExceptionHandler {
@ExceptionHandler(ResponseStatusException.class)
- private ResponseEntity responseStatusExceptionHandler(ResponseStatusException exception) {
+ 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 db226e9..a372bdb 100644
--- a/src/main/java/com/provedcode/talent/TalentController.java
+++ b/src/main/java/com/provedcode/talent/TalentController.java
@@ -4,28 +4,33 @@
import com.provedcode.talent.model.dto.ShortTalentDTO;
import com.provedcode.talent.service.TalentService;
import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
+@Slf4j
@RestController
@AllArgsConstructor
-@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT,
- RequestMethod.DELETE})
+@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
+@RequestMapping("/api")
public class TalentController {
TalentService talentService;
- @GetMapping("/api/talents/{id}")
+ @PreAuthorize("hasRole('TALENT')")
+ @GetMapping("/talents/{id}")
FullTalentDTO getTalent(@PathVariable("id") long id) {
return talentService.getTalentById(id);
}
- @GetMapping("/api/talents")
+ @GetMapping("/talents")
@ResponseStatus(HttpStatus.OK)
Page 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/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java
index 90516ac..2b0794b 100644
--- a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java
+++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java
@@ -11,28 +11,32 @@ public class TalentMapperImpl implements TalentMapper {
@Override
public 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();
+ .id(talent.getId())
+ .image(talent.getImage())
+ .firstName(talent.getFirstName())
+ .lastName(talent.getLastName())
+ .specialization(talent.getSpecialization())
+ .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList())
+ .build();
}
+
@Override
public 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();
+ .id(talent.getId())
+ .firstName(talent.getFirstName())
+ .lastName(talent.getLastName())
+ .bio(talent.getTalentDescription() != null ? talent.getTalentDescription().getBio() : null)
+ .additionalInfo(talent.getTalentDescription() != null ? talent.getTalentDescription()
+ .getAdditionalInfo() : null)
+ .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/model/dto/FullTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java
index a8a72c2..4bd9394 100644
--- a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java
+++ b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java
@@ -7,8 +7,8 @@
@Builder
public record FullTalentDTO (
Long id,
- String firstname,
- String lastname,
+ String firstName,
+ String lastName,
String image,
String specialization,
String additionalInfo,
diff --git a/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java
index a9ca9a7..491bcdb 100644
--- a/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java
+++ b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java
@@ -8,8 +8,8 @@
public record ShortTalentDTO(
Long id,
String image,
- String firstname,
- String lastname,
+ 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 f52f6f1..232de47 100644
--- a/src/main/java/com/provedcode/talent/model/entity/Talent.java
+++ b/src/main/java/com/provedcode/talent/model/entity/Talent.java
@@ -46,5 +46,4 @@ 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/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java
index af20a7f..eb1217d 100644
--- a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java
+++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java
@@ -1,23 +1,21 @@
package com.provedcode.talent.service.impl;
import com.provedcode.config.PageProperties;
-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 com.provedcode.talent.service.TalentService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
-import java.util.List;
import java.util.Optional;
-import java.util.stream.Collectors;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;
@@ -25,12 +23,14 @@
@Service
@Slf4j
@AllArgsConstructor
+@Transactional
public class TalentServiceImpl implements TalentService {
TalentMapper talentMapper;
TalentRepository talentRepository;
PageProperties pageProperties;
@Override
+ @Transactional(readOnly = true)
public Page getTalentsPage(Optional page, Optional size) {
if (page.orElse(pageProperties.defaultPageNum()) < 0) {
throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0");
@@ -38,15 +38,17 @@ public Page getTalentsPage(Optional page, Optional talent = talentRepository.findById(id);
- if (talent.isEmpty()){
+ 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/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java
new file mode 100644
index 0000000..1111d1e
--- /dev/null
+++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java
@@ -0,0 +1,32 @@
+package com.provedcode.user.controller;
+
+import com.provedcode.user.model.dto.RegistrationDTO;
+import com.provedcode.user.model.dto.SessionInfoDTO;
+import com.provedcode.user.service.AuthenticationService;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@AllArgsConstructor
+@Slf4j
+@RequestMapping("/api/talents")
+@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
+public class AuthenticationController {
+ AuthenticationService authenticationService;
+
+ @PostMapping("/login")
+ SessionInfoDTO login(Authentication authentication) {
+ return authenticationService.login(authentication.getName(), authentication.getAuthorities());
+ }
+
+ @PostMapping("/register")
+ @ResponseStatus(HttpStatus.OK)
+ SessionInfoDTO register(@RequestBody @Valid RegistrationDTO user) {
+ return authenticationService.register(user);
+ }
+
+}
diff --git a/src/main/java/com/provedcode/user/mapper/UserInfoMapper.java b/src/main/java/com/provedcode/user/mapper/UserInfoMapper.java
new file mode 100644
index 0000000..d7138b4
--- /dev/null
+++ b/src/main/java/com/provedcode/user/mapper/UserInfoMapper.java
@@ -0,0 +1,8 @@
+package com.provedcode.user.mapper;
+
+import com.provedcode.user.model.entity.UserInfo;
+import org.springframework.security.core.userdetails.UserDetails;
+
+public interface UserInfoMapper {
+ UserDetails toUserDetails(UserInfo user);
+}
diff --git a/src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java b/src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java
new file mode 100644
index 0000000..9db05e9
--- /dev/null
+++ b/src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java
@@ -0,0 +1,24 @@
+package com.provedcode.user.mapper.impl;
+
+import com.provedcode.user.mapper.UserInfoMapper;
+import com.provedcode.user.model.entity.UserInfo;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UserInfoMapperImpl implements UserInfoMapper {
+ @Override
+ public UserDetails toUserDetails(UserInfo user) {
+ return User.withUsername(user.getLogin())
+ .password(user.getPassword())
+ .authorities(user.getUserAuthorities()
+ .stream()
+ .map(i -> new SimpleGrantedAuthority(
+ i.getAuthority()
+ .getAuthority()))
+ .toList())
+ .build();
+ }
+}
diff --git a/src/main/java/com/provedcode/user/model/Role.java b/src/main/java/com/provedcode/user/model/Role.java
new file mode 100644
index 0000000..d1f1e35
--- /dev/null
+++ b/src/main/java/com/provedcode/user/model/Role.java
@@ -0,0 +1,15 @@
+package com.provedcode.user.model;
+
+public enum Role {
+ TALENT("ROLE_TALENT");
+ private final String userRole;
+
+ Role(String role) {
+ this.userRole = role;
+ }
+
+ @Override
+ public String toString() {
+ return this.userRole;
+ }
+}
diff --git a/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java
new file mode 100644
index 0000000..ae3ae5f
--- /dev/null
+++ b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java
@@ -0,0 +1,19 @@
+package com.provedcode.user.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Builder;
+
+@Builder
+public record RegistrationDTO(
+ @NotEmpty String login,
+ @NotEmpty String password,
+ @JsonProperty("first_name")
+ @NotEmpty
+ String firstName,
+ @JsonProperty("last_name")
+ @NotEmpty
+ String lastName,
+ @NotEmpty String specialization
+) {
+}
diff --git a/src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java b/src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java
new file mode 100644
index 0000000..17686d8
--- /dev/null
+++ b/src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java
@@ -0,0 +1,10 @@
+package com.provedcode.user.model.dto;
+
+import lombok.Builder;
+
+@Builder
+public record SessionInfoDTO(
+ String status,
+ String token
+) {
+}
diff --git a/src/main/java/com/provedcode/user/model/entity/Authority.java b/src/main/java/com/provedcode/user/model/entity/Authority.java
index 384c35a..dc8a965 100644
--- a/src/main/java/com/provedcode/user/model/entity/Authority.java
+++ b/src/main/java/com/provedcode/user/model/entity/Authority.java
@@ -6,9 +6,6 @@
import lombok.Getter;
import lombok.Setter;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
@Getter
@Setter
@Entity
@@ -20,6 +17,6 @@ public class Authority {
private Long id;
@NotEmpty
@NotNull
- @Column(name = "authority", length = 50)
+ @Column(name = "authority", length = 20)
private String authority;
}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java
index 5a71b00..d8bbcf7 100644
--- a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java
+++ b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java
@@ -1,12 +1,12 @@
package com.provedcode.user.model.entity;
import jakarta.persistence.*;
-import lombok.Getter;
-import lombok.Setter;
-
-import java.util.LinkedHashSet;
-import java.util.Set;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
@Getter
@Setter
@Entity
@@ -16,9 +16,11 @@ public class UserAuthority {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
+ @NotNull
@ManyToOne
@JoinColumn(name = "user_id")
private UserInfo userInfo;
+ @NotNull
@ManyToOne
@JoinColumn(name = "authority_id")
private Authority authority;
diff --git a/src/main/java/com/provedcode/user/model/entity/UserInfo.java b/src/main/java/com/provedcode/user/model/entity/UserInfo.java
index 27f9a8e..2f7efca 100644
--- a/src/main/java/com/provedcode/user/model/entity/UserInfo.java
+++ b/src/main/java/com/provedcode/user/model/entity/UserInfo.java
@@ -2,29 +2,39 @@
import com.provedcode.talent.model.entity.Talent;
import jakarta.persistence.*;
+import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
-import lombok.Getter;
-import lombok.Setter;
+import lombok.*;
import java.util.LinkedHashSet;
import java.util.Set;
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "user_info")
public class UserInfo {
- @NotNull
@Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
+ @NotNull
+ @Column(name = "user_id")
+ private Long userId;
+ @NotEmpty
+ @NotNull
@Column(name = "login", length = 100)
private String login;
+ @NotEmpty
+ @NotNull
@Column(name = "password")
private String password;
@OneToOne(orphanRemoval = true)
- @JoinColumn(name = "id")
+ @JoinColumn(name = "user_id", insertable = false, updatable = false)
private Talent talent;
- @OneToMany(mappedBy = "userInfo", orphanRemoval = true)
+ @OneToMany(mappedBy = "userInfo", orphanRemoval = true, fetch = FetchType.EAGER)
private Set userAuthorities = new LinkedHashSet<>();
}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/user/repo/AuthorityRepository.java b/src/main/java/com/provedcode/user/repo/AuthorityRepository.java
new file mode 100644
index 0000000..dbb713e
--- /dev/null
+++ b/src/main/java/com/provedcode/user/repo/AuthorityRepository.java
@@ -0,0 +1,10 @@
+package com.provedcode.user.repo;
+
+import com.provedcode.user.model.entity.Authority;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface AuthorityRepository extends JpaRepository {
+ Optional findByAuthority(String authority);
+}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java
new file mode 100644
index 0000000..ef91bfc
--- /dev/null
+++ b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java
@@ -0,0 +1,10 @@
+package com.provedcode.user.repo;
+
+import com.provedcode.user.model.entity.UserAuthority;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+import java.util.Set;
+
+public interface UserAuthorityRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java
index bb340ee..50f93ee 100644
--- a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java
+++ b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java
@@ -3,5 +3,10 @@
import com.provedcode.user.model.entity.UserInfo;
import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+
public interface UserInfoRepository extends JpaRepository {
+ boolean existsByLogin(String login);
+ Optional findByLogin(String login);
+
}
\ No newline at end of file
diff --git a/src/main/java/com/provedcode/user/service/AuthenticationService.java b/src/main/java/com/provedcode/user/service/AuthenticationService.java
new file mode 100644
index 0000000..743015d
--- /dev/null
+++ b/src/main/java/com/provedcode/user/service/AuthenticationService.java
@@ -0,0 +1,12 @@
+package com.provedcode.user.service;
+
+import com.provedcode.user.model.dto.RegistrationDTO;
+import com.provedcode.user.model.dto.SessionInfoDTO;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+public interface AuthenticationService {
+ SessionInfoDTO login(String name, Collection extends GrantedAuthority> authorities);
+ SessionInfoDTO register(RegistrationDTO user);
+}
diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java
new file mode 100644
index 0000000..b195ccd
--- /dev/null
+++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java
@@ -0,0 +1,100 @@
+package com.provedcode.user.service.impl;
+
+import com.provedcode.talent.model.entity.Talent;
+import com.provedcode.talent.repo.db.TalentEntityRepository;
+import com.provedcode.user.model.Role;
+import com.provedcode.user.model.dto.RegistrationDTO;
+import com.provedcode.user.model.dto.SessionInfoDTO;
+import com.provedcode.user.model.entity.UserAuthority;
+import com.provedcode.user.model.entity.UserInfo;
+import com.provedcode.user.repo.AuthorityRepository;
+import com.provedcode.user.repo.UserAuthorityRepository;
+import com.provedcode.user.repo.UserInfoRepository;
+import com.provedcode.user.service.AuthenticationService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.time.temporal.ChronoUnit.MINUTES;
+
+@Service
+@AllArgsConstructor
+@Slf4j
+public class AuthenticationServiceImpl implements AuthenticationService {
+ JwtEncoder jwtEncoder;
+ UserInfoRepository userInfoRepository;
+ TalentEntityRepository talentEntityRepository;
+ UserAuthorityRepository userAuthorityRepository;
+ AuthorityRepository authorityRepository;
+ PasswordEncoder passwordEncoder;
+
+ @Transactional
+ public SessionInfoDTO login(String name, Collection extends GrantedAuthority> authorities) {
+ return new SessionInfoDTO("User {%s} log-in".formatted(name), generateJWTToken(name, authorities));
+ }
+
+ @Transactional
+ public SessionInfoDTO register(RegistrationDTO user) {
+ if (userInfoRepository.existsByLogin(user.login())) {
+ throw new ResponseStatusException(HttpStatus.CONFLICT,
+ String.format("user with login = {%s} already exists", user.login()));
+ }
+ Talent talent = Talent.builder()
+ .firstName(user.firstName())
+ .lastName(user.lastName())
+ .specialization(user.specialization())
+ .build();
+ talentEntityRepository.save(talent);
+
+ UserInfo userInfo = UserInfo.builder()
+ .userId(talent.getId())
+ .login(user.login())
+ .password(passwordEncoder.encode(user.password()))
+ .build();
+ UserAuthority userAuthority = UserAuthority.builder()
+ .userInfo(userInfo)
+ .authority(authorityRepository.findByAuthority(Role.TALENT.toString())
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "this authority does`t exist")))
+ .build();
+
+ userInfo.setUserAuthorities(Set.of(userAuthority));
+ userAuthority.setUserInfo(userInfoRepository.save(userInfo));
+ userAuthorityRepository.save(userAuthority);
+
+ String userLogin = userInfo.getLogin();
+ Collection extends GrantedAuthority> userAuthorities = userInfo.getUserAuthorities().stream().map(i -> new SimpleGrantedAuthority(i.getAuthority().getAuthority())).toList();
+
+ log.info("user with login {%s} was saved, his authorities: %s".formatted(userLogin, userAuthorities));
+
+ return new SessionInfoDTO("User: {%s} was registered".formatted(userLogin), generateJWTToken(userLogin, userAuthorities));
+ }
+
+ private String generateJWTToken(String name, Collection extends GrantedAuthority> authorities) {
+ log.info("=== POST /login === auth.name = {}", name);
+ log.info("=== POST /login === auth = {}", authorities);
+ var now = Instant.now();
+ var claims = JwtClaimsSet.builder()
+ .issuer("self")
+ .issuedAt(now)
+ .expiresAt(now.plus(60, MINUTES))
+ .subject(name)
+ .claim("scope", authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" ")))
+ .build();
+ return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
+ }
+
+}
diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql
index 6b0f1ae..7e1c2fb 100644
--- a/src/main/resources/data.sql
+++ b/src/main/resources/data.sql
@@ -1,3 +1,12 @@
+insert into authority (id, authority)
+values (1, 'ROLE_TALENT');
+-- FOR USER AUTHORITY
+-- SELECT USER_INFO.ID , LOGIN , PASSWORD, USER_ID , AUTHORITY FROM
+-- USER_INFO
+-- JOIN USER_AUTHORITY ON USER_ID = USER_INFO.ID
+-- JOIN AUTHORITY ON AUTHORITY.ID = AUTHORITY_ID
+
+
insert into talent (first_name, last_name, specialization, image)
values ('Serhii', 'Soloviov', 'Java-Developer', 'https://i.pinimg.com/564x/e1/08/49/e10849923a8b2e85a7adf494ebd063e6.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -29,6 +38,12 @@ values ((select id from talent order by id desc limit 1), 'second_file');
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Mykhailo', 'Ordyntsev', 'Java-Developer', 'https://i.pinimg.com/564x/c2/41/31/c24131fe00218467721ba5bacdf0a256.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -60,6 +75,12 @@ values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_sec
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Denis', 'Boyko', 'Java-Developer', 'https://i.pinimg.com/564x/2a/0c/08/2a0c08c421e253ca895c3fdc8c9e08d9.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -89,6 +110,12 @@ values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_fil
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'DenisBoyko', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Ihor', 'Schurenko', 'Java-Developer', 'https://i.pinimg.com/564x/e1/11/2f/e1112f0b7b63644dc3e313084936dedb.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -115,6 +142,13 @@ insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_file');
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_file');
+
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'DmytroUzun', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Dmytro', 'Uzun', 'Dev-Ops', 'https://i.pinimg.com/564x/1c/af/87/1caf8771ef3edf351f6f2bf6f1c0a276.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -144,6 +178,12 @@ values ((select id from talent order by id desc limit 1), 'DmytroUzun_second_fil
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'DmytroUzun_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'DmytroUzun', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Viktor', 'Voloshko', 'Dev-Ops', 'https://i.pinimg.com/564x/a9/51/ab/a951ab682413b89617235e65564c1e5e.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -171,6 +211,12 @@ values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_second
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'ViktorVoloshko', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Olha', 'Moiseienko', 'QA', 'https://i.pinimg.com/564x/6d/9d/43/6d9d437baf4db114c047d927307beb84.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -200,6 +246,12 @@ values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_second
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko _third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Maxim', 'Kiyashko', 'QA', 'https://i.pinimg.com/564x/80/2d/58/802d58b0302985f9486893d499d3634d.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -227,6 +279,12 @@ values ((select id from talent order by id desc limit 1), 'MaximKiyashko_second_
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'MaximKiyashko_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'MaximKiyashko', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Nikolaiev', 'Oleksii', 'QA', 'https://i.pinimg.com/564x/54/d1/0d/54d10dfce64afefabc9fbbce5de82c87.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -256,6 +314,12 @@ values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_seco
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Artem', 'Lytvynenko', 'QA', 'https://i.pinimg.com/564x/87/63/55/87635509c5fa7ee496ec351fa7e67eaa.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -283,6 +347,12 @@ values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_secon
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Daniil', 'Yevtukhov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/fe/b1/37/feb137d88a3d1c8fb28796db6cbc576f.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -310,6 +380,12 @@ values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_secon
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Ruslan', 'Morozov', 'Java-Script-Developer', 'https://i.pinimg.com/736x/36/ae/0e/36ae0ea4aad656f7c3d3175bc33b8399.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -339,6 +415,12 @@ values ((select id from talent order by id desc limit 1), 'RuslanMorozov_second_
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'RuslanMorozov_third_file');
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'RuslanMorozov', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
+
insert into talent (first_name, last_name, specialization, image)
values ('Ihor', 'Kopieichykov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/0d/f0/83/0df083121bac75f64e3d93c7c5682d04.jpg');
insert into talent_description (talent_id, BIO, addition_info)
@@ -366,4 +448,10 @@ values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_firs
insert into talent_attached_file (talent_id, attached_file)
values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_second_file');
insert into talent_attached_file (talent_id, attached_file)
-values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_file');
\ No newline at end of file
+values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_file');
+
+insert into user_info (user_id, login, password)
+values ((select id from talent order by id desc limit 1), 'IhorKopieichykov', 'password');
+insert into user_authority (user_id, authority_id)
+values ((select id from user_info order by id desc limit 1),
+ (select authority.id from authority where id = 1));
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 05c6a9e..2166cfd 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -1,14 +1,14 @@
-DROP TABLE IF EXISTS talent CASCADE ;
-DROP TABLE IF EXISTS talent_description CASCADE ;
-DROP TABLE IF EXISTS talent_link CASCADE ;
-DROP TABLE IF EXISTS talent_contact CASCADE ;
-DROP TABLE IF EXISTS talent_attached_file CASCADE ;
-DROP TABLE IF EXISTS talent_skill CASCADE ;
-DROP TABLE IF EXISTS user_authority CASCADE ;
-DROP TABLE IF EXISTS user_info CASCADE ;
-DROP TABLE IF EXISTS authority CASCADE ;
-
-CREATE TABLE talent (
+drop table IF EXISTS talent CASCADE ;
+drop table IF EXISTS talent_description CASCADE ;
+drop table IF EXISTS talent_link CASCADE ;
+drop table IF EXISTS talent_contact CASCADE ;
+drop table IF EXISTS talent_attached_file CASCADE ;
+drop table IF EXISTS talent_skill CASCADE ;
+drop table IF EXISTS user_authority CASCADE ;
+drop table IF EXISTS user_info CASCADE ;
+drop table IF EXISTS authority CASCADE ;
+
+create TABLE talent (
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
first_name VARCHAR(20) NOT NULL,
last_name VARCHAR(20) NOT NULL,
@@ -64,28 +64,29 @@ create TABLE talent_attached_file (
alter table talent_attached_file add CONSTRAINT FK_TALENT_ATTACHED_FILE_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id);
--user tables--
-CREATE TABLE user_info (
- id BIGINT NOT NULL,
- login VARCHAR(100),
- password VARCHAR(255),
+create TABLE user_info (
+ id BIGINT AUTO_INCREMENT NOT NULL,
+ user_id BIGINT NOT NULL,
+ login VARCHAR(100) NOT NULL,
+ password VARCHAR(255) NOT NULL,
CONSTRAINT pk_user_info PRIMARY KEY (id)
);
-ALTER TABLE user_info ADD CONSTRAINT FK_USER_INFO_ON_ID FOREIGN KEY (id) REFERENCES talent (id);
+alter table user_info add CONSTRAINT FK_USER_INFO_ON_USER FOREIGN KEY (user_id) REFERENCES talent (id);
-CREATE TABLE authority (
+create TABLE authority (
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
- authority VARCHAR(50) NOT NULL,
+ authority VARCHAR(20) NOT NULL,
CONSTRAINT pk_authority PRIMARY KEY (id)
);
-CREATE TABLE user_authority (
+create TABLE user_authority (
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
user_id BIGINT,
authority_id BIGINT,
CONSTRAINT pk_user_authority PRIMARY KEY (id)
);
-ALTER TABLE user_authority ADD CONSTRAINT FK_USER_AUTHORITY_ON_AUTHORITY FOREIGN KEY (authority_id) REFERENCES authority (id);
+alter table user_authority add CONSTRAINT FK_USER_AUTHORITY_ON_AUTHORITY FOREIGN KEY (authority_id) REFERENCES authority (id);
-ALTER TABLE user_authority ADD CONSTRAINT FK_USER_AUTHORITY_ON_USER FOREIGN KEY (user_id) REFERENCES user_info (id);
\ No newline at end of file
+alter table user_authority add CONSTRAINT FK_USER_AUTHORITY_ON_USER FOREIGN KEY (user_id) REFERENCES user_info (id);
\ No newline at end of file