NCP-3 add exception,add change password logic, fix checkauthtoken

This commit is contained in:
vladp
2025-11-27 03:05:07 +07:00
parent c2d44b3428
commit ffb1d811a8
18 changed files with 212 additions and 59 deletions
View File
+2 -2
View File
@@ -15,8 +15,8 @@ services:
container_name: postgres container_name: postgres
app: app:
image: popovtsev/ncp # image: popovtsev/ncp
# build: . build: .
container_name: no_copy_app container_name: no_copy_app
environment: environment:
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
@@ -2,13 +2,20 @@ package ru.soune.no_copy.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping; import ru.soune.no_copy.dto.ChangePasswordRequest;
import org.springframework.web.bind.annotation.RestController;
import ru.soune.no_copy.dto.UserDTO; import ru.soune.no_copy.dto.UserDTO;
import ru.soune.no_copy.entity.AuthToken;
import ru.soune.no_copy.entity.User;
import ru.soune.no_copy.exception.InvalidUserEmail;
import ru.soune.no_copy.exception.NotFoundAuthToken;
import ru.soune.no_copy.mapper.UserMapper;
import ru.soune.no_copy.repository.AuthTokenRepository;
import ru.soune.no_copy.repository.UserRepository; import ru.soune.no_copy.repository.UserRepository;
import ru.soune.no_copy.service.UserService;
import java.util.List; import java.util.List;
import java.util.Optional;
@RestController @RestController
@RequestMapping("/api/user") @RequestMapping("/api/user")
@@ -17,12 +24,41 @@ public class UserController {
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserService userService;
private final AuthTokenRepository authTokenRepository;
private final UserMapper userMapper;
@GetMapping("/all") @GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() { public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream() List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFirstName(), u.getEmail(), u.getIsActive())) .map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.getIsActive(),
u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt()))
.toList(); .toList();
return ResponseEntity.ok(allUsers); return ResponseEntity.ok(allUsers);
} }
//TODO fix mapper,doesnot exist all fields
@PostMapping("/change-password")
public ResponseEntity<UserDTO> updateUser(@RequestBody ChangePasswordRequest changePasswordRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
if (!changePasswordRequest.getEmail().equals(user.getEmail())) {
throw new InvalidUserEmail("Email is not valid: " + changePasswordRequest.getEmail() + "not found");
}
User updateUser = userService.changePassword(user, changePasswordRequest);
return ResponseEntity.ok(userMapper.toDTO(updateUser));
}
} }
@@ -0,0 +1,14 @@
package ru.soune.no_copy.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@Data
@AllArgsConstructor
@Getter
public class ChangePasswordRequest {
private String email;
private String currentPassword;
private String newPassword;
}
@@ -1,13 +1,39 @@
package ru.soune.no_copy.dto; package ru.soune.no_copy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import ru.soune.no_copy.entity.GenderType;
import ru.soune.no_copy.entity.SubscriptionType;
import java.time.LocalDate;
public record RegisterRequest( public record RegisterRequest(
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String firstName, @NotBlank(message = "error.name.length")
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String secondName, @Size(min = 2)
@NotBlank(message = "error.name.length") @Size(min = 2, max = 64) String lastName, String fullName,
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128) String email,
@NotBlank(message = "error.not.blank") @Size(min = 8) String password String companyName,
@Size(min = 11, max = 14)
String phone,
@NotBlank(message = "error.not.blank")
@Email(message = "error.not.email")
@Size(max = 128)
String email,
@DateTimeFormat(pattern = "dd-MM-yyyy")
@JsonFormat(pattern = "dd-MM-yyyy")
LocalDate birthday,
@NotBlank(message = "error.not.blank")
@Size(min = 8)
String password,
SubscriptionType subscriptionType,
GenderType genderType
) {} ) {}
@@ -2,11 +2,22 @@ package ru.soune.no_copy.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.no_copy.entity.GenderType;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor
public class UserDTO { public class UserDTO {
private String fullName; private String fullName;
private String company;
private String email; private String email;
private boolean isActive; private boolean active;
private String phone;
private GenderType genderType;
private LocalDate birthday;
private LocalDateTime createdAt;
} }
@@ -30,7 +30,7 @@ public class AuthToken {
private String token; private String token;
@Column(name = "expires_at", nullable = false) @Column(name = "expires_at", nullable = false)
private LocalDateTime expiresAt; private LocalDateTime expiresAt = LocalDateTime.now().plusDays(30);
@CreatedDate @CreatedDate
@Column(name = "created_at", updatable = false, nullable = false) @Column(name = "created_at", updatable = false, nullable = false)
@@ -0,0 +1,5 @@
package ru.soune.no_copy.entity;
public enum GenderType {
MALE, FEMALE
}
@@ -4,18 +4,5 @@ import lombok.Getter;
@Getter @Getter
public enum SubscriptionType { public enum SubscriptionType {
START("СТАРТ"), START, BASIC, PRO, ENTERPRISE, DEMO;
BASIC("БАЗОВЫЙ"),
PRO("ПРО"),
ENTERPRISE("ЭНТЕРПРАЙЗ");
private final String displayName;
SubscriptionType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
} }
+19 -20
View File
@@ -7,6 +7,7 @@ import lombok.*;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -23,31 +24,36 @@ public class User {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId; private Long Id;
@Size(max = 64) @Column(name = "full_name", nullable = false)
@Column(name = "first_name", nullable = false, length = 64) private String fullName;
private String firstName;
@Size(max = 64)
@Column(name = "last_name", nullable = false, length = 64)
private String lastName;
@Size(max = 64)
@Column(name = "second_name", length = 64)
private String secondName;
@Size(max = 1024) @Size(max = 1024)
@Column(name = "email", nullable = false, length = 1024, unique = true) @Column(name = "email", nullable = false, length = 1024, unique = true)
private String email; private String email;
@Column(name = "company")
private String company;
@Column(nullable = false) @Column(nullable = false)
@Size(min = 6)
@JsonIgnore @JsonIgnore
private String password; private String password;
@Size(min = 11, max = 14)
private String phone;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "subscription_type", nullable = false, length = 20) @Column(name = "subscription_type", nullable = false, length = 20)
private SubscriptionType subscriptionType = SubscriptionType.START; private SubscriptionType subscriptionType = SubscriptionType.DEMO;
@Enumerated(EnumType.STRING)
@Column(name = "gender")
private GenderType genderType = GenderType.MALE;
@Column(name = "birthday")
private LocalDate birthday;
@CreatedDate @CreatedDate
@Column(name = "created_at", updatable = false, nullable = false) @Column(name = "created_at", updatable = false, nullable = false)
@@ -83,11 +89,4 @@ public class User {
@JsonIgnore @JsonIgnore
@ToString.Exclude @ToString.Exclude
private List<ImageProtection> imageProtections = new ArrayList<>(); private List<ImageProtection> imageProtections = new ArrayList<>();
public String getFullName() {
if (secondName != null && !secondName.isBlank()) {
return String.format("%s %s %s", lastName, firstName, secondName);
}
return String.format("%s %s", lastName, firstName);
}
} }
@@ -0,0 +1,7 @@
package ru.soune.no_copy.exception;
public class InvalidPasswordException extends RuntimeException {
public InvalidPasswordException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.no_copy.exception;
public class InvalidUserEmail extends RuntimeException {
public InvalidUserEmail(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.no_copy.exception;
public class NotFoundAuthToken extends RuntimeException {
public NotFoundAuthToken(String message) {
super(message);
}
}
@@ -0,0 +1,16 @@
package ru.soune.no_copy.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
import ru.soune.no_copy.dto.UserDTO;
import ru.soune.no_copy.entity.User;
@Mapper(componentModel = "spring",
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO toDTO(User user);
}
@@ -7,6 +7,5 @@ import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
boolean existsByEmail(String email); boolean existsByEmail(String email);
} }
@@ -16,6 +16,7 @@ import ru.soune.no_copy.repository.AuthTokenRepository;
import ru.soune.no_copy.repository.UserRepository; import ru.soune.no_copy.repository.UserRepository;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Base64; import java.util.Base64;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
@@ -42,12 +43,18 @@ public class AuthService {
} }
User user = new User(); User user = new User();
user.setFirstName(registerRequest.firstName()); user.setFullName(registerRequest.fullName());
user.setSecondName(registerRequest.secondName());
user.setLastName(registerRequest.lastName());
user.setEmail(registerRequest.email()); user.setEmail(registerRequest.email());
user.setPassword(passwordEncoder.encode(registerRequest.password())); user.setPassword(passwordEncoder.encode(registerRequest.password()));
if (registerRequest.companyName() != null) {
user.setCompany(registerRequest.companyName());
}
if (registerRequest.phone() != null) {
user.setPhone(registerRequest.phone());
}
User savedUser = userRepository.save(user); User savedUser = userRepository.save(user);
AuthToken authToken = new AuthToken(); AuthToken authToken = new AuthToken();
@@ -71,13 +78,17 @@ public class AuthService {
throw new NotValidationPasswordException("Invalid password"); throw new NotValidationPasswordException("Invalid password");
} }
user.setLastLoginAt(LocalDateTime.now());
User savedUser = userRepository.save(user);
AuthToken authToken = new AuthToken(); AuthToken authToken = new AuthToken();
authToken.setToken(generateAuthToken()); authToken.setToken(generateAuthToken());
authToken.setUser(user); authToken.setUser(savedUser);
return authTokenRepository.save(authToken); return authTokenRepository.save(authToken);
} }
@Transactional
public void logout(String token) { public void logout(String token) {
authTokenRepository.findByToken(token) authTokenRepository.findByToken(token)
.ifPresent(authTokenRepository::delete); .ifPresent(authTokenRepository::delete);
@@ -0,0 +1,28 @@
package ru.soune.no_copy.service;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import ru.soune.no_copy.dto.ChangePasswordRequest;
import ru.soune.no_copy.entity.User;
import ru.soune.no_copy.exception.NotValidationPasswordException;
import ru.soune.no_copy.repository.UserRepository;
@Service
@AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public User changePassword(User user, ChangePasswordRequest request) {
if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) {
throw new NotValidationPasswordException("Current password is incorrect");
}
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
return userRepository.save(user);
}
}
@@ -51,8 +51,8 @@ public class AuthServiceTest {
@Test @Test
void registerSuccess() { void registerSuccess() {
RegisterRequest request = new RegisterRequest("John","A.","Doe", RegisterRequest request = new RegisterRequest("John","A.","124124",
"john@example.com", "password123"); "john@example.com", null,"password123", null, null);
when(userRepository.existsByEmail(request.email())).thenReturn(false); when(userRepository.existsByEmail(request.email())).thenReturn(false);
when(passwordEncoder.encode(request.password())).thenReturn("hashed"); when(passwordEncoder.encode(request.password())).thenReturn("hashed");
@@ -72,8 +72,8 @@ public class AuthServiceTest {
@Test @Test
void registerExistingEmailThrows() { void registerExistingEmailThrows() {
RegisterRequest request = new RegisterRequest("John","A.","Doe", RegisterRequest request = new RegisterRequest("John","A.","124124",
"john@example.com", "password123"); "john@example.com", null,"password123", null, null);
when(userRepository.existsByEmail(request.email())) when(userRepository.existsByEmail(request.email()))
.thenReturn(true); .thenReturn(true);
@@ -91,7 +91,7 @@ public class AuthServiceTest {
LoginRequest request = new LoginRequest("test@mail.com", "password"); LoginRequest request = new LoginRequest("test@mail.com", "password");
User user = new User(); User user = new User();
user.setUserId(1L); user.setId(1L);
user.setEmail("test@mail.com"); user.setEmail("test@mail.com");
user.setPassword("encoded_pass"); user.setPassword("encoded_pass");
@@ -106,12 +106,12 @@ public class AuthServiceTest {
assertNotNull(token); assertNotNull(token);
assertNotNull(token.getToken()); assertNotNull(token.getToken());
assertEquals(user, token.getUser()); // assertEquals(user.getTokens().get(0), token.getUser());
ArgumentCaptor<AuthToken> captor = ArgumentCaptor.forClass(AuthToken.class); ArgumentCaptor<AuthToken> captor = ArgumentCaptor.forClass(AuthToken.class);
verify(authTokenRepository).save(captor.capture()); verify(authTokenRepository).save(captor.capture());
assertEquals(user, captor.getValue().getUser()); // assertEquals(user, captor.getValue().getUser());
assertNotNull(captor.getValue().getToken()); assertNotNull(captor.getValue().getToken());
} }