Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 60 additions & 52 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>cat.udl.eps.softarch</groupId>
<artifactId>spring-template</artifactId>
Expand Down Expand Up @@ -50,13 +51,13 @@
<artifactId>spring-data-rest-hal-explorer</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Expand All @@ -77,7 +78,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand All @@ -89,24 +90,24 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-api</artifactId>
<version>6.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-api</artifactId>
<version>6.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
Expand All @@ -118,36 +119,43 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

According to the custom coding guidelines, modifications to project dependencies are disallowed. The addition of the junit-jupiter-api dependency violates this guideline. This dependency should already be available through spring-boot-starter-test.

Copilot generated this review using guidance from repository custom instructions.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The custom coding guideline (ID 1000000) states: "Disallow modifications to the project dependencies." While the changes in pom.xml appear to be mostly formatting/whitespace adjustments, there is an addition of junit-jupiter-api dependency on lines 123-127. This appears to be added to support the JUnit tests. However, since this PR should be using Cucumber tests instead (as per the same guideline), this dependency addition may not be necessary. Please verify if this dependency is required or if it's only added to support the JUnit tests that should be replaced with Cucumber tests.

Copilot generated this review using guidance from repository custom instructions.

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
Expand Down Expand Up @@ -178,4 +186,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package cat.udl.eps.softarch.demo.api.dto;

import jakarta.validation.constraints.NotBlank;

public record AssignRefereeRequest(
@NotBlank(message = "matchId is mandatory") String matchId,
@NotBlank(message = "refereeId is mandatory") String refereeId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cat.udl.eps.softarch.demo.api.dto;

public record AssignRefereeResponse(String matchId, String refereeId, String status) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cat.udl.eps.softarch.demo.api.dto;

public record AssignmentErrorResponse(String error, String message) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cat.udl.eps.softarch.demo.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import cat.udl.eps.softarch.demo.api.dto.AssignRefereeRequest;
import cat.udl.eps.softarch.demo.api.dto.AssignRefereeResponse;
import cat.udl.eps.softarch.demo.service.MatchAssignmentService;
import jakarta.validation.Valid;

@Validated
@RestController
public class MatchAssignmentController {

private final MatchAssignmentService matchAssignmentService;

public MatchAssignmentController(MatchAssignmentService matchAssignmentService) {
this.matchAssignmentService = matchAssignmentService;
}

@PostMapping("/match-assignments")
@PreAuthorize("isAuthenticated()")
public AssignRefereeResponse assignReferee(@Valid @RequestBody AssignRefereeRequest request) {
return matchAssignmentService.assignReferee(request.matchId(), request.refereeId());
}
}
47 changes: 47 additions & 0 deletions src/main/java/cat/udl/eps/softarch/demo/domain/Match.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cat.udl.eps.softarch.demo.domain;

import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "competition_match")
@Getter
@Setter
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
public class Match extends UriEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@EqualsAndHashCode.Include
private Long id;

@NotNull
@Column(nullable = false)
private LocalDateTime startTime;

@NotNull
@Column(nullable = false)
private LocalDateTime endTime;

@NotNull
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private MatchState state;

@ManyToOne
@JoinColumn(name = "referee_id")
private Referee referee;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cat.udl.eps.softarch.demo.domain;

public enum MatchState {
SCHEDULED,
FINISHED
}
15 changes: 15 additions & 0 deletions src/main/java/cat/udl/eps/softarch/demo/domain/Referee.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cat.udl.eps.softarch.demo.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "referees")
@Getter
@Setter
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
public class Referee extends Volunteer {
}
1 change: 1 addition & 0 deletions src/main/java/cat/udl/eps/softarch/demo/domain/Team.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public String getId() {

@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
@ToString.Exclude
@Size(max = 10, message = "A team cannot have more than 10 members")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The @Size validation constraint on the members collection is not appropriate for runtime enforcement. The @Size annotation is a Bean Validation constraint that works on the collection size at validation time, but it doesn't prevent adding elements beyond the limit at runtime. The existing addMember method at line 87-93 already enforces this limit correctly with IllegalStateException. This annotation is redundant and could lead to confusion about where the validation actually occurs.

Suggested change
@Size(max = 10, message = "A team cannot have more than 10 members")

Copilot uses AI. Check for mistakes.
private List<TeamMember> members = new ArrayList<>();

public Team(String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cat.udl.eps.softarch.demo.exception;

public enum AssignmentErrorCode {
MATCH_NOT_FOUND,
REFEREE_NOT_FOUND,
INVALID_ROLE,
AVAILABILITY_CONFLICT,
MATCH_ALREADY_HAS_REFEREE,
INVALID_MATCH_STATE,
INVALID_ID_FORMAT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cat.udl.eps.softarch.demo.exception;

public class AssignmentValidationException extends RuntimeException {

private static final long serialVersionUID = 1L;

private final AssignmentErrorCode errorCode;

public AssignmentValidationException(AssignmentErrorCode errorCode, String message) {
super(message);
this.errorCode = errorCode;
}

public AssignmentErrorCode getErrorCode() {
return errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cat.udl.eps.softarch.demo.exception;

import org.springframework.dao.CannotAcquireLockException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import cat.udl.eps.softarch.demo.api.dto.AssignmentErrorResponse;
import cat.udl.eps.softarch.demo.controller.MatchAssignmentController;

@RestControllerAdvice(assignableTypes = MatchAssignmentController.class)
public class MatchAssignmentExceptionHandler {

@ExceptionHandler(AssignmentValidationException.class)
public ResponseEntity<AssignmentErrorResponse> handleAssignmentValidationException(
AssignmentValidationException exception) {
HttpStatus status = mapStatus(exception.getErrorCode());
AssignmentErrorResponse response = new AssignmentErrorResponse(
exception.getErrorCode().name(),
exception.getMessage());
return ResponseEntity.status(status).body(response);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<AssignmentErrorResponse> handleMethodArgumentNotValidException(
MethodArgumentNotValidException exception) {
String message = exception.getBindingResult().getFieldErrors().stream()
.findFirst()
.map(error -> error.getDefaultMessage())
.orElse("Invalid request payload");
AssignmentErrorResponse response = new AssignmentErrorResponse(
AssignmentErrorCode.INVALID_ID_FORMAT.name(),
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The handleMethodArgumentNotValidException method always maps validation errors to INVALID_ID_FORMAT error code (line 35), which is incorrect. This handler will catch any @Valid annotation validation failures on the AssignRefereeRequest, but those could include @NotBlank violations on matchId or refereeId fields, which are not necessarily format errors. The handler should either distinguish between different validation errors or use a more generic error code like INVALID_REQUEST.

Suggested change
AssignmentErrorCode.INVALID_ID_FORMAT.name(),
"INVALID_REQUEST",

Copilot uses AI. Check for mistakes.
message);
return ResponseEntity.unprocessableEntity().body(response);
}

@ExceptionHandler({
OptimisticLockingFailureException.class,
PessimisticLockingFailureException.class,
CannotAcquireLockException.class
})
public ResponseEntity<AssignmentErrorResponse> handleConcurrencyException(RuntimeException exception) {
AssignmentErrorResponse response = new AssignmentErrorResponse(
AssignmentErrorCode.MATCH_ALREADY_HAS_REFEREE.name(),
"Concurrent assignment conflict detected");
return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}

private HttpStatus mapStatus(AssignmentErrorCode errorCode) {
return switch (errorCode) {
case MATCH_NOT_FOUND, REFEREE_NOT_FOUND -> HttpStatus.NOT_FOUND;
case MATCH_ALREADY_HAS_REFEREE, AVAILABILITY_CONFLICT -> HttpStatus.CONFLICT;
case INVALID_ROLE, INVALID_MATCH_STATE, INVALID_ID_FORMAT -> HttpStatus.UNPROCESSABLE_ENTITY;
};
}
}
Loading
Loading