Skip to content

Implement Member Update Endpoint - #73

Open
Allimonae wants to merge 5 commits into
mainfrom
updatemember2
Open

Implement Member Update Endpoint#73
Allimonae wants to merge 5 commits into
mainfrom
updatemember2

Conversation

@Allimonae

@Allimonae Allimonae commented Aug 7, 2026

Copy link
Copy Markdown
Member

Implement Member Update Endpoint

Summary

Add a PATCH /members/{id} endpoint to update member profiles with support for partial updates, comprehensive validation, and proper error handling.

Changes

  • Controller: Implement updateMember endpoint with @Valid request validation
  • Service: Add business logic for partial updates with:
    • Null-check pattern (null = skip update, empty string = valid update)
    • Email uniqueness validation before update
    • Validation exceptions for blank required fields
  • DTO: Refactor UpdateMemberRequest to use Optional<String> fields to distinguish between "field not provided" (null) and "field provided as empty" (blank validation)
  • Exception Handling: Add ValidationException and handler for service-level validation failures
  • Tests: Comprehensive test coverage for both service and controller layers:
    • Service: null/empty field handling, email duplicate detection, validation edge cases
    • Controller: HTTP status codes (200, 400, 404, 409), response structure, exception integration

Design Notes

  • Partial updates: Client can omit fields to leave them unchanged
  • Required fields (firstName, lastName, email, introduction): Cannot be empty strings but can be omitted
  • Email change validation: Only checks for duplicates when email is being updated to a new value
  • All validation exceptions return 400 with descriptive error messages

Testing

  • All service tests passing (partial updates, all nulls, full updates, email scenarios)
  • All controller tests passing (happy path, validation, error scenarios)

Testing Null vs Blank

  1. Created a new member

image.png

  1. Test case 1 - all new fields

image.pngimage.png

  1. Test case 2 - all fields null (no updates)

image.pngimage.png

  1. Test case 3 - all fields blank

image.pngimage.png

  1. Test case 4 - required fields null, optional fields blank

image.pngimage.png

Allimonae commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Allimonae
Allimonae marked this pull request as ready for review August 10, 2026 20:55
@Allimonae
Allimonae requested a review from a team August 10, 2026 20:55
@Allimonae Allimonae changed the title MemberSqlRepo updateMember and getMemberById Implement Member Update Endpoint Aug 10, 2026
@graphite-app

graphite-app Bot commented Aug 10, 2026

Copy link
Copy Markdown

Graphite Automations

"Request reviewers once CI passes" took an action on this PR • (08/10/26)

2 reviewers were added to this PR based on Henry Chen's automation.

@sonarqubecloud

Copy link
Copy Markdown

}

private void updateOptional(Member member, UpdateMemberRequest request) {
request.linkedInUrl().ifPresent(member::setLinkedInUrl);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the only difference here between this and the validate function that you throw an exception? I feel like it'd be easier to have a mandatory flag and then you can use the helper for these as well.


@Override
public Optional<Member> getMemberByEmail(String email) {
String sql = "SELECT * FROM members WHERE email = :email";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not part of the PR directly but I just realized, is this just used for the create member exists check? If we just need a yes/no we could theoretically just use SELECT EXISTS without returning all of the columns?

I don't recall if email is indexed but if it is, even better

throw new ValidationException("email cannot be empty");
}
if (!email.equals(member.getEmail())
&& memberRepo.getMemberByEmail(email).isPresent()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this field already be unique in the DB?

I feel like it's better to let the DB handle it rather than do a full scan ourselves.

The only thing may be, I'm not sure if the JDBC exception lets us see which column violated the unique constraint. It is nicer that we have a clear exception that tells the user that the email is the culprit.

Regardless, adding onto my other comment about returning everything, I think this should be at least replaced with just a true/false return.

void updateMember_badRequestWhenValidationFails() throws Exception {
final UUID id = UUID.randomUUID();
final UpdateMemberRequest request = new UpdateMemberRequest(
Optional.empty(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is an actual expected case, but we probably shouldn't run an update when the update is empty (it would also change the updated at time)

maybe have a flag that we set to true only if any fields are present, and if not, we abort the update?


when(memberService.updateMember(any(), any())).thenThrow(new ValidationException("firstName cannot be empty"));

mockMvc.perform(patch("/api/members/{id}", id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you could probably have some test helpers for success/failure code to not have to rewrite this every time

void updateMember_throwsExceptionWhenEmailIsDuplicate() {
final UUID id = UUID.randomUUID();
final UUID otherMemberId = UUID.randomUUID();
final UpdateMemberRequest request = new UpdateMemberRequest(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another nit - can add constants for these in a third test helper file so you're not rewriting them for essentially the same tests in member services tests and member controller tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants