diff --git a/src/main/java/com/zenfulcode/commercify/commercify/api/requests/RegisterUserRequest.java b/src/main/java/com/zenfulcode/commercify/commercify/api/requests/RegisterUserRequest.java index 04de06e..2687b78 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/api/requests/RegisterUserRequest.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/api/requests/RegisterUserRequest.java @@ -1,24 +1,15 @@ package com.zenfulcode.commercify.commercify.api.requests; import com.zenfulcode.commercify.commercify.dto.AddressDTO; - -import java.util.UUID; +import com.zenfulcode.commercify.commercify.dto.UserDTO; public record RegisterUserRequest( String email, String password, String firstName, String lastName, - Boolean isGuest, AddressDTO defaultAddress) { - // Set a secure default password - public RegisterUserRequest { - if (password == null || password.isBlank()) { - password = UUID.randomUUID().toString(); - } - - if (isGuest == null) { - isGuest = false; - } + public UserDTO toUserDTO() { + return new UserDTO(null, email, firstName, lastName, null, defaultAddress, null); } } \ No newline at end of file diff --git a/src/main/java/com/zenfulcode/commercify/commercify/api/responses/orders/GetOrderResponse.java b/src/main/java/com/zenfulcode/commercify/commercify/api/responses/orders/GetOrderResponse.java index 9d63fba..b50e37a 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/api/responses/orders/GetOrderResponse.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/api/responses/orders/GetOrderResponse.java @@ -2,6 +2,7 @@ import com.zenfulcode.commercify.commercify.OrderStatus; +import com.zenfulcode.commercify.commercify.dto.AddressDTO; import com.zenfulcode.commercify.commercify.dto.OrderDTO; import com.zenfulcode.commercify.commercify.dto.OrderDetailsDTO; import com.zenfulcode.commercify.commercify.viewmodel.OrderLineViewModel; @@ -12,6 +13,9 @@ public record GetOrderResponse( Long id, Long userId, + String customerName, + String customerEmail, + AddressDTO shippingAddress, OrderStatus orderStatus, String currency, Double totalAmount, @@ -24,6 +28,9 @@ public static GetOrderResponse from(OrderDetailsDTO orderDetails) { return new GetOrderResponse( order.getId(), order.getUserId(), + orderDetails.getCustomerDetails().getFirstName() + " " + orderDetails.getCustomerDetails().getLastName(), + orderDetails.getCustomerDetails().getEmail(), + orderDetails.getShippingAddress(), order.getOrderStatus(), order.getCurrency(), order.getTotalAmount(), diff --git a/src/main/java/com/zenfulcode/commercify/commercify/config/SecurityConfig.java b/src/main/java/com/zenfulcode/commercify/commercify/config/SecurityConfig.java index 29119c2..87ed0e0 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/config/SecurityConfig.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/config/SecurityConfig.java @@ -35,10 +35,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers( "/api/v1/auth/**", "/api/v1/products/active", - "/api/v1/products/{id}", - "/api/v1/orders", - "/api/v1/payments/mobilepay/create", - "/api/v1/payments/stripe/create").permitAll() + "/api/v1/products/{id}").permitAll() .anyRequest().authenticated() ) .sessionManagement(smc -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) diff --git a/src/main/java/com/zenfulcode/commercify/commercify/controller/AuthenticationController.java b/src/main/java/com/zenfulcode/commercify/commercify/controller/AuthenticationController.java index d79c13a..e34813c 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/controller/AuthenticationController.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/controller/AuthenticationController.java @@ -10,7 +10,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @Slf4j @@ -25,13 +24,6 @@ public class AuthenticationController { public ResponseEntity register(@RequestBody RegisterUserRequest registerRequest) { try { UserDTO user = authenticationService.registerUser(registerRequest); - - if (registerRequest.isGuest()) { - UserDTO authenticated = authenticationService.authenticate(new LoginUserRequest(registerRequest.email(), registerRequest.password())); - String jwt = jwtService.generateToken(authenticated); - return ResponseEntity.ok(AuthResponse.UserAuthenticated(authenticated, jwt, jwtService.getExpirationTime())); - } - return ResponseEntity.ok(AuthResponse.UserAuthenticated(user, "", 0)); } catch (RuntimeException e) { log.error("Error registering user: {}", e.getMessage()); @@ -51,8 +43,23 @@ public ResponseEntity login(@RequestBody LoginUserRequest loginReq } @GetMapping("/me") - @PreAuthorize("hasRole('USER')") public ResponseEntity getAuthenticatedUser(@RequestHeader("Authorization") String authHeader) { - return ResponseEntity.ok(authenticationService.getAuthenticatedUser(authHeader)); + try { + return ResponseEntity.ok(authenticationService.getAuthenticatedUser(authHeader)); + } catch (Exception e) { + return ResponseEntity.badRequest().body(null); + } + } + + @PostMapping("/guest") + public ResponseEntity registerGuest() { + try { + UserDTO user = authenticationService.registerGuest(); + String jwt = jwtService.generateToken(user); + return ResponseEntity.ok(AuthResponse.UserAuthenticated(user, jwt, jwtService.getExpirationTime())); + } catch (RuntimeException e) { + log.error("Error registering guest: {}", e.getMessage()); + return ResponseEntity.badRequest().body(AuthResponse.AuthenticationFailed(e.getMessage())); + } } -} +} \ No newline at end of file diff --git a/src/main/java/com/zenfulcode/commercify/commercify/controller/OrderController.java b/src/main/java/com/zenfulcode/commercify/commercify/controller/OrderController.java index 3a90ea3..44430a3 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/controller/OrderController.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/controller/OrderController.java @@ -37,7 +37,7 @@ public class OrderController { "id", "userId", "status", "currency", "totalAmount", "createdAt", "updatedAt" ); - @PreAuthorize("hasRole('USER') and #userId == authentication.principal.id") + @PreAuthorize("#userId == authentication.principal.id") @PostMapping("/{userId}") public ResponseEntity createOrder(@PathVariable Long userId, @RequestBody CreateOrderRequest orderRequest) { try { @@ -86,7 +86,7 @@ public ResponseEntity createOrder(@RequestBody CreateOrderRequest orderReques } } - @PreAuthorize("hasRole('USER') and #userId == authentication.principal.id or hasRole('ADMIN')") + @PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')") @GetMapping("/user/{userId}") public ResponseEntity getOrdersByUserId( @PathVariable Long userId, @@ -143,7 +143,7 @@ public ResponseEntity getAllOrders( } } - @PreAuthorize("hasRole('USER') and @orderService.isOrderOwnedByUser(#orderId, authentication.principal.id) or hasRole('ADMIN')") + @PreAuthorize("@orderService.isOrderOwnedByUser(#orderId, authentication.principal.id) or hasRole('ADMIN')") @GetMapping("/{orderId}") public ResponseEntity getOrderById(@PathVariable Long orderId) { try { diff --git a/src/main/java/com/zenfulcode/commercify/commercify/controller/UserManagementController.java b/src/main/java/com/zenfulcode/commercify/commercify/controller/UserManagementController.java index 039f725..c4011ea 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/controller/UserManagementController.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/controller/UserManagementController.java @@ -1,6 +1,7 @@ package com.zenfulcode.commercify.commercify.controller; +import com.zenfulcode.commercify.commercify.api.requests.RegisterUserRequest; import com.zenfulcode.commercify.commercify.dto.AddressDTO; import com.zenfulcode.commercify.commercify.dto.UserDTO; import com.zenfulcode.commercify.commercify.service.UserManagementService; @@ -50,6 +51,20 @@ public ResponseEntity updateUser(@PathVariable Long id, @RequestBody Us return ResponseEntity.ok(userManagementService.updateUser(id, userDTO)); } + @PutMapping("/{id}/register") + @PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id") + public ResponseEntity registerGuest(@PathVariable Long id, @RequestBody RegisterUserRequest request) { + System.out.println("Registering guest"); + System.out.println(request); + + try { + return ResponseEntity.ok(userManagementService.updateGuest(id, request)); + } catch (Exception e) { + System.out.println("error: " + e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN')") public ResponseEntity deleteUser(@PathVariable Long id) { diff --git a/src/main/java/com/zenfulcode/commercify/commercify/entity/UserEntity.java b/src/main/java/com/zenfulcode/commercify/commercify/entity/UserEntity.java index d4f418c..4a7954e 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/entity/UserEntity.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/entity/UserEntity.java @@ -9,10 +9,7 @@ import org.springframework.security.core.userdetails.UserDetails; import java.time.Instant; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Table(name = "users") @@ -48,7 +45,7 @@ public class UserEntity implements UserDetails { @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "id")) @Column(name = "role") - private List roles; + private List roles = new ArrayList<>(); @Column(nullable = false, name = "email_confirmed") private Boolean emailConfirmed = false; @@ -72,6 +69,24 @@ public Collection getAuthorities() { .collect(Collectors.toList()); } + public void addRole(String role) { + if (roles == null) { + roles = new ArrayList<>(); + } + + if (!roles.contains(role.toUpperCase())) { + roles.add(role.toUpperCase()); + } + } + + public void removeRole(String role) { + if (roles == null) { + return; + } + + roles.remove(role.toUpperCase()); + } + @Override public String getUsername() { return email; diff --git a/src/main/java/com/zenfulcode/commercify/commercify/service/AuthenticationService.java b/src/main/java/com/zenfulcode/commercify/commercify/service/AuthenticationService.java index 1f0b6ae..32b1d4b 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/service/AuthenticationService.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/service/AuthenticationService.java @@ -16,8 +16,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.UUID; @Service @AllArgsConstructor @@ -66,6 +68,32 @@ public UserDTO registerUser(RegisterUserRequest registerRequest) { return mapper.apply(savedUser); } + public UserDTO registerGuest() { + String firstName = "Guest"; + String lastName = String.valueOf(new Date().toInstant().toEpochMilli()); + String email = firstName + lastName + "@commercify.app"; + String password = UUID.randomUUID().toString(); + + UserEntity user = UserEntity.builder() + .firstName(firstName) + .lastName(lastName) + .email(email) + .password(passwordEncoder.encode(password)) + .roles(List.of("GUEST")) + .emailConfirmed(true) + .build(); + UserEntity savedUser = userRepository.save(user); + + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + email, + password + ) + ); + + return mapper.apply(savedUser); + } + public UserDTO authenticate(LoginUserRequest login) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( diff --git a/src/main/java/com/zenfulcode/commercify/commercify/service/UserManagementService.java b/src/main/java/com/zenfulcode/commercify/commercify/service/UserManagementService.java index d7a13c7..6477bfe 100644 --- a/src/main/java/com/zenfulcode/commercify/commercify/service/UserManagementService.java +++ b/src/main/java/com/zenfulcode/commercify/commercify/service/UserManagementService.java @@ -1,6 +1,7 @@ package com.zenfulcode.commercify.commercify.service; +import com.zenfulcode.commercify.commercify.api.requests.RegisterUserRequest; import com.zenfulcode.commercify.commercify.dto.AddressDTO; import com.zenfulcode.commercify.commercify.dto.UserDTO; import com.zenfulcode.commercify.commercify.dto.mapper.AddressMapper; @@ -9,19 +10,24 @@ import com.zenfulcode.commercify.commercify.entity.UserEntity; import com.zenfulcode.commercify.commercify.repository.UserRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Optional; @Service @RequiredArgsConstructor +@Slf4j public class UserManagementService { private final UserRepository userRepository; private final UserMapper mapper; private final AddressMapper addressMapper; + private final BCryptPasswordEncoder passwordEncoder; @Transactional(readOnly = true) public UserDTO getUserById(Long id) { @@ -36,16 +42,42 @@ public Page getAllUsers(Pageable pageable) { } @Transactional - public UserDTO updateUser(Long id, UserDTO userDTO) { + public UserDTO updateUser(Long id, UserDTO userDTO) throws RuntimeException { // Explicitly declare throws UserEntity user = userRepository.findById(id) .orElseThrow(() -> new RuntimeException("User not found")); + Optional existing = userRepository.findByEmail(userDTO.getEmail()); + + if (existing.isPresent() && !existing.get().getId().equals(id)) { // Add check for same user + throw new RuntimeException("User with email " + userDTO.getEmail() + " already exists"); + } + user.setFirstName(userDTO.getFirstName()); user.setLastName(userDTO.getLastName()); user.setEmail(userDTO.getEmail()); - UserEntity updatedUser = userRepository.save(user); - return mapper.apply(updatedUser); + return mapper.apply(userRepository.save(user)); + } + + @Transactional + public UserDTO updateGuest(Long id, RegisterUserRequest request) { + try { + updateUser(id, request.toUserDTO()); + + UserEntity user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("User not found")); + + user.setPassword(passwordEncoder.encode(request.password())); + user.removeRole("GUEST"); + user.addRole("USER"); + + UserEntity updatedUser = userRepository.save(user); + return mapper.apply(updatedUser); + } catch (RuntimeException e) { + // Log the error + log.error("Failed to update guest user: {}", e.getMessage(), e); + throw e; // Re-throw the exception instead of swallowing it + } } @Transactional diff --git a/src/test/java/com/zenfulcode/commercify/commercify/service/AuthenticationServiceTest.java b/src/test/java/com/zenfulcode/commercify/commercify/service/AuthenticationServiceTest.java index 06feca8..933cc05 100644 --- a/src/test/java/com/zenfulcode/commercify/commercify/service/AuthenticationServiceTest.java +++ b/src/test/java/com/zenfulcode/commercify/commercify/service/AuthenticationServiceTest.java @@ -77,7 +77,6 @@ void setUp() { "password123", "John", "Doe", - false, shippingAddress ); @@ -127,9 +126,7 @@ void registerUser_Success() { void registerUser_NoPasswordProvided_ShouldSetDefaultPassword() { // Arrange RegisterUserRequest request = new RegisterUserRequest( - "test@example.com", "", "Test", "User", - false, - null); + "test@example.com", "", "Test", "User", null); when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty()); when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");