Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -10,6 +11,6 @@
public class LoginRequest {
@NotBlank @Email
private String email;
@NotBlank
@NotBlank @Size(max = 100, message = "must not exceed 100 characters")
private String password;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.springboot.MyTodoList.common.exception;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -70,13 +72,31 @@ public ResponseEntity<ErrorResponse> handleBadCredentials(BadCredentialsExceptio
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex, HttpServletRequest req) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
.collect(Collectors.joining(", "));
log.warn("[400] {} {}: {}", req.getMethod(), req.getRequestURI(), message);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse("VALIDATION_ERROR", message, 400));
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolation(ConstraintViolationException ex, HttpServletRequest req) {
String message = ex.getConstraintViolations().stream()
.map(this::formatViolation)
.collect(Collectors.joining(", "));
log.warn("[400] {} {}: {}", req.getMethod(), req.getRequestURI(), message);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse("VALIDATION_ERROR", message, 400));
}

private String formatViolation(ConstraintViolation<?> violation) {
String path = violation.getPropertyPath().toString();
// Property path for method params looks like "method.paramName"; keep only the last node.
int lastDot = path.lastIndexOf('.');
String field = lastDot >= 0 ? path.substring(lastDot + 1) : path;
return field + ": " + violation.getMessage();
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex, HttpServletRequest req) {
log.error("[500] {} {} — {}", req.getMethod(), req.getRequestURI(), ex.getMessage(), ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/projects/{projectId}/dashboard")
@RequiredArgsConstructor
@Validated
@Tag(name = "Dashboard")
@SecurityRequirement(name = "bearerAuth")
public class DashboardController {
Expand All @@ -30,7 +34,9 @@ public ResponseEntity<SprintSummaryResponse> sprintSummary(@PathVariable Long pr
@GetMapping("/velocity")
@Operation(summary = "SP completed per closed sprint for the last N sprints, oldest to newest")
public ResponseEntity<List<VelocityResponse>> velocity(@PathVariable Long projectId,
@RequestParam(defaultValue = "5") int sprints,
@RequestParam(defaultValue = "5")
@Min(value = 1, message = "must be between 1 and 50")
@Max(value = 50, message = "must be between 1 and 50") int sprints,
Authentication auth) {
return ResponseEntity.ok(dashboardService.getVelocity(uid(auth), projectId, sprints));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.springboot.MyTodoList.task;

import com.springboot.MyTodoList.common.enums.TaskStatus;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -12,5 +15,8 @@
public class StatusChangeRequest {
@NotNull
private TaskStatus status;
@Positive(message = "must be greater than 0")
@DecimalMax(value = "999.99", message = "must not exceed 999.99")
@Digits(integer = 4, fraction = 2, message = "allows at most 2 decimal places")
private BigDecimal actualHours;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.springboot.MyTodoList.common.enums.TaskPriority;
import com.springboot.MyTodoList.common.enums.TaskStatus;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
Expand All @@ -19,6 +21,8 @@ public class TaskRequest {
private TaskStatus status;
private TaskPriority priority;
@NotNull
@Min(value = 1, message = "must be between 1 and 21")
@Max(value = 21, message = "must be between 1 and 21")
private Integer storyPoints;
private Long assignedTo;
private Long sprintId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Validated
@Tag(name = "Users")
@SecurityRequirement(name = "bearerAuth")
public class UserController {
Expand All @@ -32,7 +37,8 @@ public ResponseEntity<UserResponse> updateMe(@Valid @RequestBody UserRequest req

@GetMapping("/search")
@Operation(summary = "Search user by email")
public ResponseEntity<UserSummary> searchByEmail(@RequestParam String email) {
public ResponseEntity<UserSummary> searchByEmail(
@RequestParam @NotBlank @Email @Size(max = 100, message = "must be a valid address (max 100 characters)") String email) {
return ResponseEntity.ok(userService.findByEmail(email));
}

Expand Down
5 changes: 5 additions & 0 deletions MtdrSpring/backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ spring:
properties:
hibernate:
format_sql: false
data:
web:
pageable:
default-page-size: 20
max-page-size: 500

jwt:
secret: ${JWT_SECRET}
Expand Down
Loading