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
@@ -0,0 +1,75 @@
package net.hackyourfuture.tickettrackingsystem.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import net.hackyourfuture.tickettrackingsystem.dto.request.CreateTicketRequest;
import net.hackyourfuture.tickettrackingsystem.dto.request.UpdateTicketRequest;
import net.hackyourfuture.tickettrackingsystem.dto.response.TicketResponse;
import net.hackyourfuture.tickettrackingsystem.service.TicketService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/tickets")
public class TicketController {

private final TicketService ticketService;

@PostMapping
public ResponseEntity<TicketResponse> createTicket(
@Valid @RequestBody CreateTicketRequest request
) {
TicketResponse response = ticketService.createTicket(request);

return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@GetMapping
public List<TicketResponse> getAllTickets() {
return ticketService.getAllTickets();
}

@GetMapping("/{id}")
public TicketResponse getTicketById(@PathVariable Long id) {
return ticketService.getTicketById(id);
}

@PutMapping("/{id}")
public TicketResponse updateTicket(
@PathVariable Long id,
@Valid @RequestBody UpdateTicketRequest request
) {
return ticketService.updateTicket(id, request);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTicket(@PathVariable Long id) {
ticketService.deleteTicket(id);

return ResponseEntity.noContent().build();
}

@PostMapping("/{ticketId}/assignees/{userId}")
public ResponseEntity<Void> assignUserToTicket(
@PathVariable Long ticketId,
@PathVariable Long userId
) {
ticketService.assignUserToTicket(ticketId, userId);

return ResponseEntity.noContent().build();
}

@DeleteMapping("/{ticketId}/assignees/{userId}")
public ResponseEntity<Void> unassignUserFromTicket(
@PathVariable Long ticketId,
@PathVariable Long userId
) {
ticketService.unassignUserFromTicket(ticketId, userId);

return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import net.hackyourfuture.tickettrackingsystem.dto.request.CreateUserRequest;
import net.hackyourfuture.tickettrackingsystem.dto.request.UpdateUserRequest;
import net.hackyourfuture.tickettrackingsystem.dto.response.UserResponse;
import net.hackyourfuture.tickettrackingsystem.model.User;
import net.hackyourfuture.tickettrackingsystem.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -20,41 +19,35 @@ public class UserController {

private final UserService userService;

private UserResponse toUserResponse(User user) {
return new UserResponse(
user.id(),
user.name(),
user.email()
);
}
@GetMapping
public List<UserResponse> getAllUsers() {
return userService.getAllUsers()
.stream()
.map(this::toUserResponse).
toList();
return userService.getAllUsers();
}

@GetMapping("/{id}")
public UserResponse getUserById(@PathVariable Long id) {
return toUserResponse(
userService.getUserById(id)
);
return userService.getUserById(id);
}

@PostMapping
public ResponseEntity<UserResponse> createUser( @Valid @RequestBody CreateUserRequest request) {
User user = userService.createUser(request);

return ResponseEntity.status(HttpStatus.CREATED)
.body(toUserResponse(user));
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody CreateUserRequest request
) {
UserResponse response = userService.createUser(request);

return ResponseEntity
.status(HttpStatus.CREATED)
.body(response);
}

@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(@PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) {
User user = userService.updateUser(id, request);
public ResponseEntity<UserResponse> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request
) {
UserResponse response = userService.updateUser(id, request);

return ResponseEntity.ok(toUserResponse(user));
return ResponseEntity.ok(response);
}

@DeleteMapping("/{id}")
Expand All @@ -63,4 +56,4 @@ public ResponseEntity<Void> deleteUser(@PathVariable Long id) {

return ResponseEntity.noContent().build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.hackyourfuture.tickettrackingsystem.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import net.hackyourfuture.tickettrackingsystem.model.TicketStatus;

public record CreateTicketRequest(
@NotBlank String title,
String description,
@NotNull Long projectId,
@NotNull TicketStatus status
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.hackyourfuture.tickettrackingsystem.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import net.hackyourfuture.tickettrackingsystem.model.TicketStatus;

public record UpdateTicketRequest(
@NotBlank String title,
String description,
@NotNull Long projectId,
@NotNull TicketStatus status
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.hackyourfuture.tickettrackingsystem.dto.response;

import java.time.LocalDateTime;
import java.util.Map;

public record ErrorResponse(
LocalDateTime timestamp,
int status,
String error,
String message,
String path,
Map<String, String> validationErrors
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.hackyourfuture.tickettrackingsystem.dto.response;

import net.hackyourfuture.tickettrackingsystem.model.TicketStatus;

import java.time.LocalDateTime;
import java.util.List;

public record TicketResponse(
Long id,
String title,
String description,
Long projectId,
TicketStatus status,
List<Long> assignedUserIds,
LocalDateTime creationDate,
LocalDateTime updateDate
) {
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,141 @@
package net.hackyourfuture.tickettrackingsystem.exception;

import jakarta.servlet.http.HttpServletRequest;
import net.hackyourfuture.tickettrackingsystem.dto.response.ErrorResponse;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, String>> handleResourceNotFound(
ResourceNotFoundException ex) {

return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("message", ex.getMessage()));
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
ResourceNotFoundException exception,
HttpServletRequest request
) {
return buildErrorResponse(
HttpStatus.NOT_FOUND,
exception.getMessage(),
request.getRequestURI(),
null
);
}

@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<Map<String, String>> handleDuplicateEmail(
DuplicateEmailException ex) {
public ResponseEntity<ErrorResponse> handleDuplicateEmailException(
DuplicateEmailException exception,
HttpServletRequest request
) {
return buildErrorResponse(
HttpStatus.CONFLICT,
exception.getMessage(),
request.getRequestURI(),
null
);
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(
IllegalArgumentException exception,
HttpServletRequest request
) {
return buildErrorResponse(
HttpStatus.BAD_REQUEST,
exception.getMessage(),
request.getRequestURI(),
null
);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException exception,
HttpServletRequest request
) {
Map<String, String> validationErrors = new LinkedHashMap<>();

for (FieldError fieldError : exception.getBindingResult().getFieldErrors()) {
validationErrors.putIfAbsent(
fieldError.getField(),
Objects.requireNonNullElse(
fieldError.getDefaultMessage(),
"Invalid value"
)
);
}

return buildErrorResponse(
HttpStatus.BAD_REQUEST,
"Validation failed",
request.getRequestURI(),
validationErrors
);
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(
HttpServletRequest request
) {
return buildErrorResponse(
HttpStatus.BAD_REQUEST,
"Invalid request body",
request.getRequestURI(),
null
);
}

@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ErrorResponse> handleDataIntegrityViolationException(
HttpServletRequest request
) {
return buildErrorResponse(
HttpStatus.CONFLICT,
"Database constraint violation",
request.getRequestURI(),
null
);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
HttpServletRequest request
) {
return buildErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR,
"Something went wrong",
request.getRequestURI(),
null
);
}

private ResponseEntity<ErrorResponse> buildErrorResponse(
HttpStatus status,
String message,
String path,
Map<String, String> validationErrors
) {
ErrorResponse response = new ErrorResponse(
LocalDateTime.now(),
status.value(),
status.getReasonPhrase(),
message,
path,
validationErrors
);

return ResponseEntity.status(HttpStatus.CONFLICT)
.body(Map.of("message", ex.getMessage()));
return ResponseEntity
.status(status)
.body(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.hackyourfuture.tickettrackingsystem.model;

import java.time.LocalDateTime;

public record Ticket(
Long id,
String title,
String description,
Long projectId,
TicketStatus status,
LocalDateTime creationDate,
LocalDateTime updateDate
) {
}
Loading
Loading