fix feature devis

This commit is contained in:
David-Henri ARNAUD 2025-10-01 10:25:11 +02:00
parent f31f1b6c69
commit c0b1548226
10 changed files with 426 additions and 22 deletions

View File

@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RestController @RestController
@RequestMapping(name = "/", @RequestMapping(value = "/",
produces = APPLICATION_JSON_VALUE) produces = APPLICATION_JSON_VALUE)
public class IndexRestController { public class IndexRestController {

View File

@ -22,6 +22,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.security.Principal; import java.security.Principal;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@ -120,13 +121,30 @@ public class UserRestController {
@RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) UUID companyId) { @RequestParam(required = false) UUID companyId) {
log.info("User list request - page: {}, size: {}, companyId: {}", page, size, companyId); log.info("User list request - page: {}, size: {}, companyId: {}", page, size, companyId);
List<UserAccount> userAccounts;
long totalElements;
if (companyId != null) {
userAccounts = userService.findUsersByCompany(companyId, page, size);
totalElements = userService.countUsersByCompany(companyId);
} else {
userAccounts = userService.findAllUsers(page, size);
totalElements = userService.countAllUsers();
}
// Convert to UserResponse DTOs
List<UserResponse> userResponses = userAccounts.stream()
.map(this::mapToUserResponse)
.collect(java.util.stream.Collectors.toList());
// Create Page object
Pageable pageable = PageRequest.of(page, size); Pageable pageable = PageRequest.of(page, size);
Page<UserResponse> users = new org.springframework.data.domain.PageImpl<>(
// TODO: Implement pagination and company filtering in service layer userResponses,
// For now, return an empty page pageable,
Page<UserResponse> users = Page.empty(pageable); totalElements);
return ResponseEntity.ok(users); return ResponseEntity.ok(users);
} }

View File

@ -6,30 +6,39 @@ import com.dh7789dev.xpeditis.dto.request.RegisterRequest;
import com.dh7789dev.xpeditis.port.in.UserManagementUseCase; import com.dh7789dev.xpeditis.port.in.UserManagementUseCase;
import java.security.Principal; import java.security.Principal;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface UserService extends UserManagementUseCase { public interface UserService extends UserManagementUseCase {
void changePassword(ChangePasswordRequest request, Principal connectedUser); void changePassword(ChangePasswordRequest request, Principal connectedUser);
UserAccount createUser(RegisterRequest request); UserAccount createUser(RegisterRequest request);
UserAccount createGoogleUser(String googleToken); UserAccount createGoogleUser(String googleToken);
Optional<UserAccount> findById(UUID id); Optional<UserAccount> findById(UUID id);
Optional<UserAccount> findByEmail(String email); Optional<UserAccount> findByEmail(String email);
Optional<UserAccount> findByUsername(String username); Optional<UserAccount> findByUsername(String username);
UserAccount updateProfile(UserAccount userAccount); UserAccount updateProfile(UserAccount userAccount);
void deactivateUser(UUID userId); void deactivateUser(UUID userId);
void deleteUser(UUID userId); void deleteUser(UUID userId);
boolean existsByEmail(String email); boolean existsByEmail(String email);
boolean existsByUsername(String username); boolean existsByUsername(String username);
List<UserAccount> findAllUsers(int page, int size);
List<UserAccount> findUsersByCompany(UUID companyId, int page, int size);
long countAllUsers();
long countUsersByCompany(UUID companyId);
} }

View File

@ -85,4 +85,24 @@ public class UserServiceImpl implements UserService {
public boolean existsByUsername(String username) { public boolean existsByUsername(String username) {
return userRepository.existsByUsername(username); return userRepository.existsByUsername(username);
} }
@Override
public java.util.List<UserAccount> findAllUsers(int page, int size) {
return userRepository.findAllUsers(page, size);
}
@Override
public java.util.List<UserAccount> findUsersByCompany(UUID companyId, int page, int size) {
return userRepository.findUsersByCompany(companyId, page, size);
}
@Override
public long countAllUsers() {
return userRepository.countAllUsers();
}
@Override
public long countUsersByCompany(UUID companyId) {
return userRepository.countUsersByCompany(companyId);
}
} }

View File

@ -29,6 +29,14 @@ public interface UserRepository {
void deleteById(UUID id); void deleteById(UUID id);
void deactivateUser(UUID id); void deactivateUser(UUID id);
List<UserAccount> findByCompanyIdAndIsActive(UUID companyId, boolean isActive); List<UserAccount> findByCompanyIdAndIsActive(UUID companyId, boolean isActive);
List<UserAccount> findAllUsers(int page, int size);
List<UserAccount> findUsersByCompany(UUID companyId, int page, int size);
long countAllUsers();
long countUsersByCompany(UUID companyId);
} }

View File

@ -0,0 +1,61 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.CompanyRepository;
import com.dh7789dev.xpeditis.dao.CompanyDao;
import com.dh7789dev.xpeditis.dto.app.Company;
import com.dh7789dev.xpeditis.entity.CompanyEntity;
import com.dh7789dev.xpeditis.mapper.CompanyMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Slf4j
@Repository
@RequiredArgsConstructor
public class CompanyJpaRepository implements CompanyRepository {
private final CompanyDao companyDao;
@Override
public Company save(Company company) {
CompanyEntity entity = CompanyMapper.companyToCompanyEntity(company);
CompanyEntity savedEntity = companyDao.save(entity);
log.info("Company saved with ID: {}", savedEntity.getId());
return CompanyMapper.companyEntityToCompany(savedEntity);
}
@Override
public Optional<Company> findById(UUID id) {
return companyDao.findById(id)
.map(CompanyMapper::companyEntityToCompany);
}
@Override
public Optional<Company> findByName(String name) {
return companyDao.findByName(name)
.map(CompanyMapper::companyEntityToCompany);
}
@Override
public List<Company> findAll() {
return companyDao.findAll().stream()
.map(CompanyMapper::companyEntityToCompany)
.collect(Collectors.toList());
}
@Override
public boolean existsByName(String name) {
return companyDao.existsByName(name);
}
@Override
public void deleteById(UUID id) {
companyDao.deleteById(id);
log.info("Company deleted with ID: {}", id);
}
}

View File

@ -0,0 +1,73 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.OAuth2Provider;
import com.dh7789dev.xpeditis.dto.app.GoogleUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Optional;
@Slf4j
@Service
public class GoogleOAuth2Provider implements OAuth2Provider {
@Value("${application.oauth2.google.user-info-uri:https://www.googleapis.com/oauth2/v2/userinfo}")
private String userInfoUri;
private final RestTemplate restTemplate;
public GoogleOAuth2Provider() {
this.restTemplate = new RestTemplate();
}
@Override
public boolean validateToken(String accessToken) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<GoogleUserInfo> response = restTemplate.exchange(
userInfoUri,
HttpMethod.GET,
entity,
GoogleUserInfo.class
);
return response.getStatusCode() == HttpStatus.OK;
} catch (Exception e) {
log.error("Error validating Google token", e);
return false;
}
}
@Override
public Optional<GoogleUserInfo> getUserInfo(String accessToken) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<GoogleUserInfo> response = restTemplate.exchange(
userInfoUri,
HttpMethod.GET,
entity,
GoogleUserInfo.class
);
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
log.info("Successfully retrieved Google user info for email: {}", response.getBody().getEmail());
return Optional.of(response.getBody());
}
log.warn("Failed to retrieve Google user info: status={}", response.getStatusCode());
return Optional.empty();
} catch (Exception e) {
log.error("Error retrieving Google user info", e);
return Optional.empty();
}
}
}

View File

@ -0,0 +1,71 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.LicenseRepository;
import com.dh7789dev.xpeditis.dao.LicenseDao;
import com.dh7789dev.xpeditis.dto.app.License;
import com.dh7789dev.xpeditis.entity.LicenseEntity;
import com.dh7789dev.xpeditis.mapper.LicenseMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Slf4j
@Repository
@RequiredArgsConstructor
public class LicenseJpaRepository implements LicenseRepository {
private final LicenseDao licenseDao;
@Override
public License save(License license) {
LicenseEntity entity = LicenseMapper.licenseToLicenseEntity(license);
LicenseEntity savedEntity = licenseDao.save(entity);
log.info("License saved with ID: {}", savedEntity.getId());
return LicenseMapper.licenseEntityToLicense(savedEntity);
}
@Override
public Optional<License> findById(UUID id) {
return licenseDao.findById(id)
.map(LicenseMapper::licenseEntityToLicense);
}
@Override
public Optional<License> findActiveLicenseByCompanyId(UUID companyId) {
return licenseDao.findActiveLicenseByCompanyId(companyId)
.map(LicenseMapper::licenseEntityToLicense);
}
@Override
public List<License> findByCompanyId(UUID companyId) {
return licenseDao.findByCompanyId(companyId).stream()
.map(LicenseMapper::licenseEntityToLicense)
.collect(Collectors.toList());
}
@Override
public Optional<License> findByLicenseKey(String licenseKey) {
return licenseDao.findByLicenseKey(licenseKey)
.map(LicenseMapper::licenseEntityToLicense);
}
@Override
public void deleteById(UUID id) {
licenseDao.deleteById(id);
log.info("License deleted with ID: {}", id);
}
@Override
public void deactivateLicense(UUID id) {
licenseDao.findById(id).ifPresent(license -> {
license.setActive(false);
licenseDao.save(license);
log.info("License deactivated with ID: {}", id);
});
}
}

View File

@ -0,0 +1,144 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.UserRepository;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.dto.app.UserAccount;
import com.dh7789dev.xpeditis.dto.request.ChangePasswordRequest;
import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Repository;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Slf4j
@Repository
@RequiredArgsConstructor
public class UserJpaRepository implements UserRepository {
private final UserDao userDao;
private final PasswordEncoder passwordEncoder;
@Override
public void changePassword(ChangePasswordRequest request, Principal connectedUser) {
String username = connectedUser.getName();
UserEntity user = userDao.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
// Verify current password
if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) {
throw new RuntimeException("Current password is incorrect");
}
// Verify new password matches confirmation
if (!request.getNewPassword().equals(request.getConfirmationPassword())) {
throw new RuntimeException("New password and confirmation do not match");
}
// Update password
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
userDao.save(user);
log.info("Password changed successfully for user: {}", username);
}
@Override
public UserAccount save(UserAccount userAccount) {
UserEntity entity = UserMapper.userAccountToUserEntity(userAccount);
UserEntity savedEntity = userDao.save(entity);
return UserMapper.userEntityToUserAccount(savedEntity);
}
@Override
public Optional<UserAccount> findById(UUID id) {
return userDao.findById(id).map(UserMapper::userEntityToUserAccount);
}
@Override
public Optional<UserAccount> findByEmail(String email) {
return userDao.findByEmail(email).map(UserMapper::userEntityToUserAccount);
}
@Override
public Optional<UserAccount> findByUsername(String username) {
return userDao.findByUsername(username).map(UserMapper::userEntityToUserAccount);
}
@Override
public Optional<UserAccount> findByGoogleId(String googleId) {
return userDao.findByGoogleId(googleId).map(UserMapper::userEntityToUserAccount);
}
@Override
public boolean existsByEmail(String email) {
return userDao.existsByEmail(email);
}
@Override
public boolean existsByUsername(String username) {
return userDao.existsByUsername(username);
}
@Override
public void deleteById(UUID id) {
userDao.deleteById(id);
log.info("User deleted with ID: {}", id);
}
@Override
public void deactivateUser(UUID id) {
UserEntity user = userDao.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
user.setEnabled(false);
userDao.save(user);
log.info("User deactivated with ID: {}", id);
}
@Override
public List<UserAccount> findByCompanyIdAndIsActive(UUID companyId, boolean isActive) {
// This needs to be implemented in UserDao
log.warn("findByCompanyIdAndIsActive not yet implemented in UserDao");
return List.of();
}
@Override
public List<UserAccount> findAllUsers(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
Page<UserEntity> userPage = userDao.findAll(pageable);
return userPage.getContent().stream()
.map(UserMapper::userEntityToUserAccount)
.collect(Collectors.toList());
}
@Override
public List<UserAccount> findUsersByCompany(UUID companyId, int page, int size) {
// For now, return all users filtered by company (needs DAO method)
Pageable pageable = PageRequest.of(page, size);
Page<UserEntity> userPage = userDao.findAll(pageable);
return userPage.getContent().stream()
.filter(user -> user.getCompany() != null && user.getCompany().getId().equals(companyId))
.map(UserMapper::userEntityToUserAccount)
.collect(Collectors.toList());
}
@Override
public long countAllUsers() {
return userDao.count();
}
@Override
public long countUsersByCompany(UUID companyId) {
// For now, count all and filter (needs DAO method for optimization)
return userDao.findAll().stream()
.filter(user -> user.getCompany() != null && user.getCompany().getId().equals(companyId))
.count();
}
}

View File

@ -41,9 +41,9 @@
</scm> </scm>
<properties> <properties>
<java.version>23</java.version> <java.version>21</java.version>
<maven.compiler.source>23</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<org.projectlombok.version>1.18.36</org.projectlombok.version> <org.projectlombok.version>1.18.36</org.projectlombok.version>