Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerPatchDTO;
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerDTO;
import ar.com.nanotaboada.java.samples.spring.boot.services.PlayersService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -219,7 +221,43 @@ public ResponseEntity<Void> put(@PathVariable Integer squadNumber, @RequestBody
? ResponseEntity.status(HttpStatus.NO_CONTENT).build()
: ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
/*
* -----------------------------------------------------------------------------------------------------------------------
* HTTP PATCH
* -----------------------------------------------------------------------------------------------------------------------
*/

/**
* Partially updates an existing player resource identified by squad number.
* <p>
* Only the fields present in the request body are updated; absent fields retain
* their current values. The {@code squadNumber} and {@code id} fields are immutable
* and must not be included in the request body.
* </p>
*
* @param squadNumber the squad number (natural key) of the player to patch
* @param playerPatchDTO the partial player data to apply (only non-null fields are applied)
* @return 204 No Content if successful, 400 Bad Request if body contains squadNumber or id,
* or 404 Not Found if player doesn't exist
*/
@PatchMapping("/players/{squadNumber}")
@Operation(summary = "Partially updates a player by squad number")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "No Content", content = @Content),
@ApiResponse(responseCode = "400", description = "Bad Request - Body must not contain squadNumber or id", content = @Content),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content)
})
public ResponseEntity<Void> patch(
@PathVariable Integer squadNumber,
@RequestBody PlayerPatchDTO playerPatchDTO) {
if (playerPatchDTO.getSquadNumber() != null || playerPatchDTO.getId() != null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
boolean patched = playersService.patch(squadNumber, playerPatchDTO);
return (patched)
? ResponseEntity.status(HttpStatus.NO_CONTENT).build()
: ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
/*
* -----------------------------------------------------------------------------------------------------------------------
* HTTP DELETE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ar.com.nanotaboada.java.samples.spring.boot.models;

import java.time.LocalDate;
import java.util.UUID;

import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.Data;

/**
* Data Transfer Object for partial updates to a {@link Player} resource (HTTP PATCH).
* <p>
* All fields are nullable. Only non-null fields are applied to the existing entity;
* absent fields (null) are left unchanged — following RFC 7396 (JSON Merge Patch) semantics.
* </p>
*
* <h3>Immutable Fields:</h3>
* <ul>
* <li>{@code id} — surrogate UUID key, must not be present in PATCH requests</li>
* <li>{@code squadNumber} — natural key, must not be present in PATCH requests</li>
* </ul>
* <p>
* If either immutable field is present in the request body, the controller returns
* {@code 400 Bad Request}.
* </p>
*
* @see Player
* @see PlayerDTO
* @since 4.0.2025
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PlayerPatchDTO {

private UUID id;
private Integer squadNumber;
private String firstName;
private String middleName;
private String lastName;
private LocalDate dateOfBirth;
private String position;
private String abbrPosition;
private String team;
private String league;
private Boolean starting11;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import ar.com.nanotaboada.java.samples.spring.boot.models.Player;
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerDTO;
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerPatchDTO;
import ar.com.nanotaboada.java.samples.spring.boot.repositories.PlayersRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -215,6 +216,47 @@
});
}

/**
* Partially updates an existing player identified by their squad number.
* <p>
* Only the non-null fields in the DTO are applied to the existing entity.
* Fields absent from the request (null) are left unchanged.
* </p>
*
* @param squadNumber the squad number (natural key) of the player to patch
* @param playerPatchDTO the partial player data to apply
* @return true if the player was patched successfully, false if not found
*/
@Transactional
@CacheEvict(value = "players", allEntries = true)
public boolean patch(Integer squadNumber, PlayerPatchDTO playerPatchDTO) {

Check failure on line 232 in src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ4MWlftwyfbI-4OXVvq&open=AZ4MWlftwyfbI-4OXVvq&pullRequest=329
log.debug("Patching player with squad number: {}", squadNumber);

if (squadNumber == null) {
log.warn("Cannot patch player - squad number is null");
return false;
}

return playersRepository.findBySquadNumber(squadNumber)
.map(existing -> {
if (playerPatchDTO.getFirstName() != null) existing.setFirstName(playerPatchDTO.getFirstName());
if (playerPatchDTO.getMiddleName() != null) existing.setMiddleName(playerPatchDTO.getMiddleName());
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (playerPatchDTO.getLastName() != null) existing.setLastName(playerPatchDTO.getLastName());
if (playerPatchDTO.getDateOfBirth() != null) existing.setDateOfBirth(playerPatchDTO.getDateOfBirth());
if (playerPatchDTO.getPosition() != null) existing.setPosition(playerPatchDTO.getPosition());
if (playerPatchDTO.getAbbrPosition() != null) existing.setAbbrPosition(playerPatchDTO.getAbbrPosition());
if (playerPatchDTO.getTeam() != null) existing.setTeam(playerPatchDTO.getTeam());
if (playerPatchDTO.getLeague() != null) existing.setLeague(playerPatchDTO.getLeague());
if (playerPatchDTO.getStarting11() != null) existing.setStarting11(playerPatchDTO.getStarting11());
playersRepository.save(existing);
log.info("Player patched successfully - Squad Number: {}", squadNumber);
return true;
})
.orElseGet(() -> {
log.warn("Cannot patch player - squad number {} not found", squadNumber);
return false;
});
}
/*
* -----------------------------------------------------------------------------------------------------------------------
* Delete
Expand Down