From b99a6c50b00c095720c6d9b10caefd03de644bef Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 19 Apr 2026 12:50:54 -0400 Subject: [PATCH] Improved user profile endpoints and validated usernames --- .../controllers/UserProfileController.java | 84 ++++++++++++++++--- .../userprofile/PostUserProfileRequest.java | 10 +++ .../userprofile/PostUserProfileResponse.java | 9 ++ .../repositories/UserProfileRepository.java | 4 +- .../backend/services/UserProfileService.java | 39 ++++++++- .../exceptions/BadRequestException.java | 7 ++ .../exceptions/NotFoundException.java | 7 ++ 7 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileRequest.java create mode 100644 backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileResponse.java create mode 100644 backend/src/main/java/net/kpuig/concord/backend/services/exceptions/BadRequestException.java create mode 100644 backend/src/main/java/net/kpuig/concord/backend/services/exceptions/NotFoundException.java diff --git a/backend/src/main/java/net/kpuig/concord/backend/controllers/UserProfileController.java b/backend/src/main/java/net/kpuig/concord/backend/controllers/UserProfileController.java index adfb15e..1365f37 100644 --- a/backend/src/main/java/net/kpuig/concord/backend/controllers/UserProfileController.java +++ b/backend/src/main/java/net/kpuig/concord/backend/controllers/UserProfileController.java @@ -1,14 +1,29 @@ package net.kpuig.concord.backend.controllers; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; +import java.net.URI; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; import net.kpuig.concord.backend.datamodels.userprofile.GetAllUserProfilesResponse; import net.kpuig.concord.backend.datamodels.userprofile.GetUserProfileResponse; +import net.kpuig.concord.backend.datamodels.userprofile.PostUserProfileRequest; +import net.kpuig.concord.backend.datamodels.userprofile.PostUserProfileResponse; import net.kpuig.concord.backend.services.UserProfileService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.RequestParam; +import net.kpuig.concord.backend.services.exceptions.BadRequestException; + @@ -18,14 +33,61 @@ public class UserProfileController { @Autowired private UserProfileService userProfileService; + @Operation(summary = "Get all user profiles", description = "Retrieves a list of all user profiles") + @ApiResponse(responseCode = "200", description = "User profiles retrieved", + content = { @Content(mediaType = "application/json") }) @GetMapping("/") - public GetAllUserProfilesResponse getAllUserProfiles() { - return userProfileService.getAllUserProfiles(); + public ResponseEntity getAllUserProfiles() { + return ResponseEntity.ok(userProfileService.getAllUserProfiles()); } - @GetMapping("/{id}") - public GetUserProfileResponse getMethodName(@RequestParam String id) { - return userProfileService.getUserProfile(id); + @Operation(summary = "Get user profile by ID or username", description = "Retrieves a user profile by its ID or username") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "User profile retrieved", + content = { @Content(mediaType = "application/json") }), + @ApiResponse(responseCode = "404", description = "User profile not found") + }) + @GetMapping("") + public ResponseEntity getUserProfileById( + @RequestParam(name = "id", required = false) Long id, + @RequestParam(name = "username", required = false) String username) { + if (id == null && username == null) { // Must provide at least one of them + return ResponseEntity.badRequest().build(); + } else if (id != null && username != null) { // Both have been provided + return ResponseEntity.badRequest().build(); + } else if (id != null) { // ID has been provided + try { + var response = userProfileService.getUserProfileById(id); + return ResponseEntity.ok(response); + } catch (NumberFormatException e) { + return ResponseEntity.notFound().build(); + } + } else { // Username has been provided + try { + var response = userProfileService.getUserProfileByUsername(username); + return ResponseEntity.ok(response); + } catch (NumberFormatException e) { + return ResponseEntity.notFound().build(); + } + } + } + + @Operation(summary = "Create user profile", description = "Creates a new user profile") + @ApiResponse(responseCode = "201", description = "User profile created", + content = { @Content(mediaType = "application/json") }) + @PostMapping("/") + public ResponseEntity createUserProfile(@RequestBody @Valid PostUserProfileRequest request) { + try { + var response = userProfileService.createUserProfile(request.getUsername()); + URI location = UriComponentsBuilder + .fromPath("/user-profile") + .queryParam("username", response.getUsername()) + .build() + .encode() + .toUri(); + return ResponseEntity.created(location).body(response); + } catch (BadRequestException e) { + return ResponseEntity.badRequest().build(); + } } - } diff --git a/backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileRequest.java b/backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileRequest.java new file mode 100644 index 0000000..2fce0f3 --- /dev/null +++ b/backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileRequest.java @@ -0,0 +1,10 @@ +package net.kpuig.concord.backend.datamodels.userprofile; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PostUserProfileRequest { + @NotBlank + private String username; +} diff --git a/backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileResponse.java b/backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileResponse.java new file mode 100644 index 0000000..e6e3629 --- /dev/null +++ b/backend/src/main/java/net/kpuig/concord/backend/datamodels/userprofile/PostUserProfileResponse.java @@ -0,0 +1,9 @@ +package net.kpuig.concord.backend.datamodels.userprofile; + +import lombok.Data; + +@Data +public class PostUserProfileResponse { + private Long id; + private String username; +} diff --git a/backend/src/main/java/net/kpuig/concord/backend/repositories/UserProfileRepository.java b/backend/src/main/java/net/kpuig/concord/backend/repositories/UserProfileRepository.java index d2624e3..dccc824 100644 --- a/backend/src/main/java/net/kpuig/concord/backend/repositories/UserProfileRepository.java +++ b/backend/src/main/java/net/kpuig/concord/backend/repositories/UserProfileRepository.java @@ -1,6 +1,6 @@ package net.kpuig.concord.backend.repositories; -import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -9,5 +9,5 @@ import net.kpuig.concord.backend.datamodels.userprofile.UserProfile; @Repository public interface UserProfileRepository extends JpaRepository { - + Optional findByUsername(String username); } diff --git a/backend/src/main/java/net/kpuig/concord/backend/services/UserProfileService.java b/backend/src/main/java/net/kpuig/concord/backend/services/UserProfileService.java index 056eb70..8820977 100644 --- a/backend/src/main/java/net/kpuig/concord/backend/services/UserProfileService.java +++ b/backend/src/main/java/net/kpuig/concord/backend/services/UserProfileService.java @@ -1,13 +1,14 @@ package net.kpuig.concord.backend.services; -import java.util.List; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import net.kpuig.concord.backend.datamodels.userprofile.GetAllUserProfilesResponse; import net.kpuig.concord.backend.datamodels.userprofile.GetUserProfileResponse; +import net.kpuig.concord.backend.datamodels.userprofile.PostUserProfileResponse; +import net.kpuig.concord.backend.datamodels.userprofile.UserProfile; import net.kpuig.concord.backend.repositories.UserProfileRepository; +import net.kpuig.concord.backend.services.exceptions.BadRequestException; @Service public class UserProfileService { @@ -25,12 +26,42 @@ public class UserProfileService { return response; } - public GetUserProfileResponse getUserProfile(String id) { + public GetUserProfileResponse getUserProfileById(Long id) { GetUserProfileResponse response = new GetUserProfileResponse(); - userProfileRepository.findById(Long.parseLong(id)).ifPresent(userProfile -> { + userProfileRepository.findById(id).ifPresentOrElse(userProfile -> { response.setId(userProfile.getId()); response.setUsername(userProfile.getUsername()); + }, () -> { + throw new net.kpuig.concord.backend.services.exceptions.NotFoundException("User profile not found"); }); return response; } + + public GetUserProfileResponse getUserProfileByUsername(String username) { + GetUserProfileResponse response = new GetUserProfileResponse(); + userProfileRepository.findByUsername(username).ifPresentOrElse(userProfile -> { + response.setId(userProfile.getId()); + response.setUsername(userProfile.getUsername()); + }, () -> { + throw new net.kpuig.concord.backend.services.exceptions.NotFoundException("User profile not found"); + }); + return response; + } + + public PostUserProfileResponse createUserProfile(String username) { + if (userProfileRepository.findByUsername(username).isPresent()) { + throw new BadRequestException("Username is already taken"); + } + + UserProfile userProfile = new UserProfile(); + userProfile.setUsername(username); + userProfile = userProfileRepository.save(userProfile); + + PostUserProfileResponse response = new PostUserProfileResponse(); + response.setId(userProfile.getId()); + response.setUsername(userProfile.getUsername()); + + return response; + } + } diff --git a/backend/src/main/java/net/kpuig/concord/backend/services/exceptions/BadRequestException.java b/backend/src/main/java/net/kpuig/concord/backend/services/exceptions/BadRequestException.java new file mode 100644 index 0000000..3027c14 --- /dev/null +++ b/backend/src/main/java/net/kpuig/concord/backend/services/exceptions/BadRequestException.java @@ -0,0 +1,7 @@ +package net.kpuig.concord.backend.services.exceptions; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/net/kpuig/concord/backend/services/exceptions/NotFoundException.java b/backend/src/main/java/net/kpuig/concord/backend/services/exceptions/NotFoundException.java new file mode 100644 index 0000000..fb77384 --- /dev/null +++ b/backend/src/main/java/net/kpuig/concord/backend/services/exceptions/NotFoundException.java @@ -0,0 +1,7 @@ +package net.kpuig.concord.backend.services.exceptions; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +}