From b9b7c6cfe2cd79ae94254e0b1c6303d6016e5c76 Mon Sep 17 00:00:00 2001 From: Austin Snyder Date: Sun, 23 Nov 2025 22:06:09 -0800 Subject: [PATCH 1/3] simplified the auth service dto structure. now only have a UserRequest and UserResponse --- .../controller/AuthController.java | 25 +++++++++++------- .../dto/EmailChangeRequestDTO.java | 15 ----------- .../dto/EmailChangeResponseDTO.java | 10 ------- .../dto/RegistrationRequestDTO.java | 19 -------------- ...ginRequestDTO.java => UserRequestDto.java} | 2 +- ...nResponseDTO.java => UserResponseDto.java} | 6 +++-- .../com/cm/authservice/mapper/UserMapper.java | 20 ++++++++++++++ .../cm/authservice/service/AuthService.java | 26 +++++++++++-------- .../cm/authservice/service/UserService.java | 26 ++++++++++++------- 9 files changed, 73 insertions(+), 76 deletions(-) delete mode 100644 auth-service/src/main/java/com/cm/authservice/dto/EmailChangeRequestDTO.java delete mode 100644 auth-service/src/main/java/com/cm/authservice/dto/EmailChangeResponseDTO.java delete mode 100644 auth-service/src/main/java/com/cm/authservice/dto/RegistrationRequestDTO.java rename auth-service/src/main/java/com/cm/authservice/dto/{LoginRequestDTO.java => UserRequestDto.java} (94%) rename auth-service/src/main/java/com/cm/authservice/dto/{RegistrationResponseDTO.java => UserResponseDto.java} (56%) create mode 100644 auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java diff --git a/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java b/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java index 558fc6a..1476b45 100644 --- a/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java +++ b/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java @@ -1,9 +1,8 @@ package com.cm.authservice.controller; -import com.cm.authservice.dto.EmailChangeRequestDTO; -import com.cm.authservice.dto.EmailChangeResponseDTO; -import com.cm.authservice.dto.LoginRequestDTO; import com.cm.authservice.dto.LoginResponseDTO; +import com.cm.authservice.dto.UserRequestDto; +import com.cm.authservice.dto.UserResponseDto; import com.cm.authservice.model.User; import com.cm.authservice.service.AuthService; import io.swagger.v3.oas.annotations.Operation; @@ -24,7 +23,7 @@ public AuthController(AuthService authService){ @PostMapping("/login") @Operation(summary = "Generate token on user login") - public ResponseEntity login(@RequestBody LoginRequestDTO loginRequestDTO){ + public ResponseEntity login(@RequestBody UserRequestDto loginRequestDTO){ Optional tokenOptional = authService.authenticate(loginRequestDTO); if(tokenOptional.isEmpty()){ @@ -53,18 +52,26 @@ public ResponseEntity validateToken(@RequestHeader("Authorization") String return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } + @PostMapping("/register") + @Operation(summary = "Register a new user") + public ResponseEntity register(@RequestBody UserRequestDto userRequestDto){ + return authService.register(userRequestDto); + } + @PutMapping("/update-email") @Operation(summary = "Update user account email.") - public ResponseEntity updateEmail(@RequestHeader("Authorization") String authHeader, - @RequestBody EmailChangeRequestDTO emailChangeRequestDTO){ + public ResponseEntity updateEmail(@RequestHeader("Authorization") String authHeader, + @RequestBody UserRequestDto userRequestDto){ if(authHeader == null || !authHeader.startsWith("Bearer ")) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // Now check if the token came from the person who claimed to want to change their email. - EmailChangeResponseDTO emailChangeResponseDTO = authService - .updateEmail(authHeader.substring(7), emailChangeRequestDTO); + UserResponseDto userResponseDto = authService + .updateEmail(authHeader.substring(7), userRequestDto); - return ResponseEntity.ok().body(emailChangeResponseDTO); + return ResponseEntity.ok().body(userResponseDto); } + + } diff --git a/auth-service/src/main/java/com/cm/authservice/dto/EmailChangeRequestDTO.java b/auth-service/src/main/java/com/cm/authservice/dto/EmailChangeRequestDTO.java deleted file mode 100644 index 9ccfc58..0000000 --- a/auth-service/src/main/java/com/cm/authservice/dto/EmailChangeRequestDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cm.authservice.dto; - -import jakarta.validation.constraints.Email; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class EmailChangeRequestDTO { - @Email - private String oldEmail; - - @Email - private String newEmail; -} diff --git a/auth-service/src/main/java/com/cm/authservice/dto/EmailChangeResponseDTO.java b/auth-service/src/main/java/com/cm/authservice/dto/EmailChangeResponseDTO.java deleted file mode 100644 index 7d4f8f8..0000000 --- a/auth-service/src/main/java/com/cm/authservice/dto/EmailChangeResponseDTO.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.cm.authservice.dto; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class EmailChangeResponseDTO { - private String savedEmail; -} diff --git a/auth-service/src/main/java/com/cm/authservice/dto/RegistrationRequestDTO.java b/auth-service/src/main/java/com/cm/authservice/dto/RegistrationRequestDTO.java deleted file mode 100644 index 2fd8391..0000000 --- a/auth-service/src/main/java/com/cm/authservice/dto/RegistrationRequestDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cm.authservice.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class RegistrationRequestDTO { - @NotBlank(message = "Email is required") - @Email(message = "Email should be valid") - private String email; - - @NotBlank(message = "Password is required") - @Size(min = 8, message = "Password must be at least 8 characters long") - private String password; -} diff --git a/auth-service/src/main/java/com/cm/authservice/dto/LoginRequestDTO.java b/auth-service/src/main/java/com/cm/authservice/dto/UserRequestDto.java similarity index 94% rename from auth-service/src/main/java/com/cm/authservice/dto/LoginRequestDTO.java rename to auth-service/src/main/java/com/cm/authservice/dto/UserRequestDto.java index 51abd68..e2d2453 100644 --- a/auth-service/src/main/java/com/cm/authservice/dto/LoginRequestDTO.java +++ b/auth-service/src/main/java/com/cm/authservice/dto/UserRequestDto.java @@ -8,7 +8,7 @@ @Getter @Setter -public class LoginRequestDTO { +public class UserRequestDto { @NotBlank(message = "Email is required") @Email(message = "Email should be valid") private String email; diff --git a/auth-service/src/main/java/com/cm/authservice/dto/RegistrationResponseDTO.java b/auth-service/src/main/java/com/cm/authservice/dto/UserResponseDto.java similarity index 56% rename from auth-service/src/main/java/com/cm/authservice/dto/RegistrationResponseDTO.java rename to auth-service/src/main/java/com/cm/authservice/dto/UserResponseDto.java index 6aa0ebd..ebcf3e8 100644 --- a/auth-service/src/main/java/com/cm/authservice/dto/RegistrationResponseDTO.java +++ b/auth-service/src/main/java/com/cm/authservice/dto/UserResponseDto.java @@ -1,12 +1,14 @@ package com.cm.authservice.dto; import lombok.Getter; +import lombok.Setter; @Getter -public class RegistrationResponseDTO { +@Setter +public class UserResponseDto { private final String email; - public RegistrationResponseDTO(String email){ + public UserResponseDto(String email){ this.email = email; } } diff --git a/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java b/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java new file mode 100644 index 0000000..6856b72 --- /dev/null +++ b/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java @@ -0,0 +1,20 @@ +package com.cm.authservice.mapper; + +import com.cm.authservice.dto.UserRequestDto; +import com.cm.authservice.dto.UserResponseDto; +import com.cm.authservice.model.User; + +public class UserMapper { + + public User fromDto(UserRequestDto userRequestDto){ + User user = new User(); + user.setPassword(userRequestDto.getPassword()); + user.setEmail(userRequestDto.getEmail()); + + return user; + } + + public UserResponseDto toDto(User user){ + return new UserResponseDto(user.getEmail()); + } +} diff --git a/auth-service/src/main/java/com/cm/authservice/service/AuthService.java b/auth-service/src/main/java/com/cm/authservice/service/AuthService.java index 213522c..5d7128f 100644 --- a/auth-service/src/main/java/com/cm/authservice/service/AuthService.java +++ b/auth-service/src/main/java/com/cm/authservice/service/AuthService.java @@ -1,15 +1,14 @@ package com.cm.authservice.service; -import com.cm.authservice.dto.EmailChangeRequestDTO; -import com.cm.authservice.dto.EmailChangeResponseDTO; -import com.cm.authservice.dto.LoginRequestDTO; -import com.cm.authservice.exception.TokenEmailDoesNotMatchException; +import com.cm.authservice.dto.*; import com.cm.authservice.exception.UserNotFoundException; import com.cm.authservice.model.User; import com.cm.authservice.util.JwtUtil; import io.jsonwebtoken.JwtException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -31,7 +30,7 @@ public AuthService(UserService userService, this.jwtUtil = jwtUtil; } - public Optional authenticate(LoginRequestDTO loginRequestDTO){ + public Optional authenticate(UserRequestDto loginRequestDTO){ log.info("Authenticating a user: {}", loginRequestDTO.getEmail()); // If user password matches, map the user to transform it into a token, which gets assigned to the optional. @@ -54,17 +53,13 @@ public boolean validateToken(String token){ } } - public EmailChangeResponseDTO updateEmail(String token, EmailChangeRequestDTO emailChangeRequestDTO) { + public UserResponseDto updateEmail(String token, UserRequestDto userRequestDto) { // Validate token and check if it belongs to same person. User user = userService.findById(jwtUtil.getIdFromToken(token)) .orElseThrow(() -> new UserNotFoundException("User not found.")); - if(!user.getEmail().equalsIgnoreCase(emailChangeRequestDTO.getOldEmail())){ - throw new TokenEmailDoesNotMatchException("Current account email does not match given old email."); - } - - return userService.updateEmail(user, emailChangeRequestDTO); + return userService.updateEmail(user, userRequestDto); } public User getUser(String token) { @@ -73,4 +68,13 @@ public User getUser(String token) { return userService.findById(authUserId) .orElseThrow(() -> new UserNotFoundException("User not found.")); } + + public ResponseEntity register(UserRequestDto registrationRequestDto) { + String salt = BCrypt.gensalt(); + + String passwordHash = + BCrypt.hashpw(registrationRequestDto.getPassword(), BCrypt.gensalt()); + + return userService.registerUser(registrationRequestDto.getEmail(), passwordHash); + } } diff --git a/auth-service/src/main/java/com/cm/authservice/service/UserService.java b/auth-service/src/main/java/com/cm/authservice/service/UserService.java index 22caec0..c7a53e6 100644 --- a/auth-service/src/main/java/com/cm/authservice/service/UserService.java +++ b/auth-service/src/main/java/com/cm/authservice/service/UserService.java @@ -1,11 +1,13 @@ package com.cm.authservice.service; +import com.cm.authservice.dto.UserRequestDto; +import com.cm.authservice.dto.UserResponseDto; import com.cm.authservice.exception.EmailAlreadyExistsException; -import com.cm.authservice.exception.UserNotFoundException; import com.cm.authservice.dto.EmailChangeRequestDTO; import com.cm.authservice.dto.EmailChangeResponseDTO; import com.cm.authservice.model.User; import com.cm.authservice.repository.UserRepository; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.util.Optional; import java.util.UUID; @@ -22,22 +24,28 @@ public Optional findByEmail(String email){ return userRepository.findByEmail(email); } - public EmailChangeResponseDTO updateEmail(User user, EmailChangeRequestDTO emailChangeRequestDTO){ + public UserResponseDto updateEmail(User user, UserRequestDto userRequestDto){ - if(userRepository.existsByEmail(emailChangeRequestDTO.getNewEmail())){ - throw new EmailAlreadyExistsException("User already exists with email: " + emailChangeRequestDTO.getNewEmail()); + if(userRepository.existsByEmail(userRequestDto.getEmail())){ + throw new EmailAlreadyExistsException("User already exists with email: " + userRequestDto.getEmail()); } - user.setEmail(emailChangeRequestDTO.getNewEmail()); + user.setEmail(userRequestDto.getEmail()); User updatedUser = userRepository.save(user); - EmailChangeResponseDTO emailChangeResponseDTO = new EmailChangeResponseDTO(); - emailChangeResponseDTO.setSavedEmail(updatedUser.getEmail()); - - return emailChangeResponseDTO; + return new UserResponseDto(updatedUser.getEmail()); } public Optional findById(UUID id) { return userRepository.findById(id); } + + public ResponseEntity registerUser(String email, String passwordHash) { + if(userRepository.existsByEmail(email)){ + throw new EmailAlreadyExistsException("This email is already taken: " + email); + } + + User newUser = userRepository.save() + + } } From bc977ceb1df18d887bae22fdeb64e6c7c84b0660 Mon Sep 17 00:00:00 2001 From: Austin Snyder Date: Sun, 23 Nov 2025 23:16:02 -0800 Subject: [PATCH 2/3] register endpoint --- .../cm/authservice/controller/AuthController.java | 4 ++-- .../java/com/cm/authservice/mapper/UserMapper.java | 4 ++-- .../com/cm/authservice/service/AuthService.java | 9 +++++---- .../com/cm/authservice/service/UserService.java | 14 ++++++++------ 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java b/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java index 1476b45..8c80c1e 100644 --- a/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java +++ b/auth-service/src/main/java/com/cm/authservice/controller/AuthController.java @@ -40,7 +40,6 @@ public ResponseEntity validateToken(@RequestHeader("Authorization") String if(authHeader == null || !authHeader.startsWith("Bearer ")) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - if(authService.validateToken(authHeader.substring(7))){ User user = authService.getUser(authHeader.substring(7)); @@ -55,7 +54,8 @@ public ResponseEntity validateToken(@RequestHeader("Authorization") String @PostMapping("/register") @Operation(summary = "Register a new user") public ResponseEntity register(@RequestBody UserRequestDto userRequestDto){ - return authService.register(userRequestDto); + UserResponseDto response = authService.register(userRequestDto); + return ResponseEntity.ok().body(response); } @PutMapping("/update-email") diff --git a/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java b/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java index 6856b72..f2c0a1a 100644 --- a/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java +++ b/auth-service/src/main/java/com/cm/authservice/mapper/UserMapper.java @@ -6,7 +6,7 @@ public class UserMapper { - public User fromDto(UserRequestDto userRequestDto){ + public static User fromDto(UserRequestDto userRequestDto){ User user = new User(); user.setPassword(userRequestDto.getPassword()); user.setEmail(userRequestDto.getEmail()); @@ -14,7 +14,7 @@ public User fromDto(UserRequestDto userRequestDto){ return user; } - public UserResponseDto toDto(User user){ + public static UserResponseDto toDto(User user){ return new UserResponseDto(user.getEmail()); } } diff --git a/auth-service/src/main/java/com/cm/authservice/service/AuthService.java b/auth-service/src/main/java/com/cm/authservice/service/AuthService.java index 5d7128f..5b35cb6 100644 --- a/auth-service/src/main/java/com/cm/authservice/service/AuthService.java +++ b/auth-service/src/main/java/com/cm/authservice/service/AuthService.java @@ -69,12 +69,13 @@ public User getUser(String token) { .orElseThrow(() -> new UserNotFoundException("User not found.")); } - public ResponseEntity register(UserRequestDto registrationRequestDto) { - String salt = BCrypt.gensalt(); - + public UserResponseDto register(UserRequestDto registrationRequestDto) { String passwordHash = BCrypt.hashpw(registrationRequestDto.getPassword(), BCrypt.gensalt()); - return userService.registerUser(registrationRequestDto.getEmail(), passwordHash); + return + userService.registerUser(registrationRequestDto.getEmail(), passwordHash); + + } } diff --git a/auth-service/src/main/java/com/cm/authservice/service/UserService.java b/auth-service/src/main/java/com/cm/authservice/service/UserService.java index c7a53e6..aa7a177 100644 --- a/auth-service/src/main/java/com/cm/authservice/service/UserService.java +++ b/auth-service/src/main/java/com/cm/authservice/service/UserService.java @@ -2,12 +2,9 @@ import com.cm.authservice.dto.UserRequestDto; import com.cm.authservice.dto.UserResponseDto; import com.cm.authservice.exception.EmailAlreadyExistsException; - -import com.cm.authservice.dto.EmailChangeRequestDTO; -import com.cm.authservice.dto.EmailChangeResponseDTO; +import com.cm.authservice.mapper.UserMapper; import com.cm.authservice.model.User; import com.cm.authservice.repository.UserRepository; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.util.Optional; import java.util.UUID; @@ -40,12 +37,17 @@ public Optional findById(UUID id) { return userRepository.findById(id); } - public ResponseEntity registerUser(String email, String passwordHash) { + public UserResponseDto registerUser(String email, String passwordHash) { if(userRepository.existsByEmail(email)){ throw new EmailAlreadyExistsException("This email is already taken: " + email); } - User newUser = userRepository.save() + User user = new User(); + user.setPassword(passwordHash); + user.setEmail(email); + + User newUser = userRepository.save(user); + return UserMapper.toDto(newUser); } } From 3b1181aa7e7bda941375e691b9c8109191ad21d9 Mon Sep 17 00:00:00 2001 From: Austin Snyder Date: Mon, 24 Nov 2025 12:59:10 -0800 Subject: [PATCH 3/3] updated readme --- README.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8856eb5..0ba7bc1 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,120 @@ - Client Service: This holds most of the business domain logic. - Auth Service: This handles JWT authentication for access to API endpoints. - - API Gateway: This routes requests to allow a single external entrypoint for users. Additionally, it ensures authorization by routing requests to the auth service before hitting other endpoints. \ No newline at end of file + - API Gateway: This routes requests to allow a single external entrypoint for users. Additionally, it ensures authorization by routing requests to the auth service before hitting other endpoints. + +# Running the Microservices in IntelliJ + +This setup assumes each service will be run through IntelliJ using Dockerfile run configurations. + +## General Instructions + +1. Make sure Docker Desktop is running. +2. In IntelliJ, create a Dockerfile run configuration for each service: + Edit Configurations -> Add (+) -> Dockerfile. +3. Select the correct Dockerfile for each service and set the required environment variables and run options. +4. Start the database containers first. Then start the services in any order. + +--- + +## API Gateway + +**Dockerfile** +api-gateway/Dockerfile + +**Environment Variables** +- AUTH_SERVICE_URL=http://auth-service:4005 + +**Run Options** +- --network coach + +--- + +## Client Service + +**Dockerfile** +client-service/Dockerfile + +**Environment Variables** +- AUTH_SERVICE_URL=http://auth-service:4005 +- SPRING_DATASOURCE_PASSWORD=password +- SPRING_DATASOURCE_URL=jdbc:postgresql://client-service-db:5432/db +- SPRING_DATASOURCE_USERNAME=admin_user +- SPRING_JPA_HIBERNATE_DDL_AUTO=update +- SPRING_SQL_INIT_MODE=always + +**Run Options** +- --network coach + +--- + +## Auth Service + +**Dockerfile** +auth-service/Dockerfile + +**Environment Variables** +- JWT_SECRET={YOUR_JWT_SECRET_HERE} +- SPRING_DATASOURCE_PASSWORD=password +- SPRING_DATASOURCE_URL=jdbc:postgresql://auth-service-db:5432/db +- SPRING_DATASOURCE_USERNAME=admin_user +- SPRING_JPA_HIBERNATE_DDL_AUTO=update +- SPRING_SQL_INIT_MODE=always + +**Run Options** +- --network coach + +--- + +## Billing Service (WIP) + +**Dockerfile** +billing-service/Dockerfile + +**Environment Variables** +- STRIPE_SECRET_KEY={YOUR_STRIPE_SECRET_KEY} + +**Run Options** +- --network coach + +--- + +## Auth Service Database + +**Base Image** +postgres:latest + +**Port Bindings** +- 5002:5432 + +**Bind Mounts** +.../db_volumes/auth-service-db → /var/lib/postgresql/ + +**Run Options** +- --network coach + +--- + +## Client Service Database + +**Base Image** +postgres:latest + +**Port Bindings** +- 5001:5432 + +**Bind Mounts** +.../db_volumes/client-service-db → /var/lib/postgresql/ + +**Run Options** +- --network coach + +--- + +## Running Everything + +1. Start the database containers: + client-service-db and auth-service-db. +2. Start the application services: + auth-service, client-service, api-gateway, and billing-service. +3. Use IntelliJ run configurations or the Docker CLI. \ No newline at end of file