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,24 @@
package pl.skyroster.skyroster_backend.application.pilot;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import pl.skyroster.skyroster_backend.domain.exception.PilotAlreadyExistsException;
import pl.skyroster.skyroster_backend.domain.port.PilotRepository;
import pl.skyroster.skyroster_backend.generated.model.PilotRequest;
import pl.skyroster.skyroster_backend.infrastructure.mappers.PilotMapper;
import pl.skyroster.skyroster_backend.generated.model.PilotResponse;
@Service
@RequiredArgsConstructor
public class AddPilotUseCase {
private final PilotRepository pilotRepository;

@Transactional
public PilotResponse addPilot(PilotRequest request) {
if (pilotRepository.existsByLicence(request.getLicence())){
throw new PilotAlreadyExistsException("Pilot already exists");
}
var pilot = PilotMapper.toEntity(request);
return PilotMapper.toResponse(pilotRepository.save(pilot));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package pl.skyroster.skyroster_backend.application.pilot;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import pl.skyroster.skyroster_backend.domain.exception.PilotNotFoundException;
import pl.skyroster.skyroster_backend.domain.port.FlightRepository;
import pl.skyroster.skyroster_backend.domain.port.PilotRepository;

import java.time.OffsetDateTime;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class DeletePilotUseCase {

private final PilotRepository pilotRepository;
private final FlightRepository flightRepository;

@Transactional
public void deletePilotById(UUID pilotId) {
if (!pilotRepository.existsById(pilotId)) {
throw new PilotNotFoundException(pilotId);
}

if(pilotHasScheduledFlights(pilotId))
throw new IllegalStateException("Pilot has ongoing or incoming flights");

pilotRepository.deleteById(pilotId);
}

private boolean pilotHasScheduledFlights(UUID pilotId){
return flightRepository.existsByPilotIdAndFlightEndAfter(pilotId, OffsetDateTime.now());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package pl.skyroster.skyroster_backend.application.pilot;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import pl.skyroster.skyroster_backend.domain.model.Pilot;
import pl.skyroster.skyroster_backend.domain.port.PilotRepository;
import pl.skyroster.skyroster_backend.generated.model.PagedPilotResponse;
import pl.skyroster.skyroster_backend.infrastructure.mappers.PilotMapper;

@Service
@RequiredArgsConstructor
public class GetPilotUseCase {

private final PilotRepository pilotRepository;

public PagedPilotResponse getPilots(Integer page, Integer size, String sort) {
Pageable pageable = createPageable(page, size, sort);

Page<Pilot> pilotsPage = pilotRepository.findAllWithRelations(pageable);

return new PagedPilotResponse()
.content(
pilotsPage.getContent()
.stream()
.map(PilotMapper::toResponse)
.toList()
)
.page(pilotsPage.getNumber())
.size(pilotsPage.getSize())
.totalElements(pilotsPage.getTotalElements())
.totalPages(pilotsPage.getTotalPages())
.first(pilotsPage.isFirst())
.last(pilotsPage.isLast());
}

private Pageable createPageable(Integer page, Integer size, String sort) {
int resolvedPage = page == null ? 0 : page;
int resolvedSize = size == null ? 20 : Math.min(size, 100);

Sort resolvedSort = Sort.by(Sort.Direction.ASC, "lastName");

if (sort != null && !sort.isBlank()) {
String[] parts = sort.split(",");
String property = parts[0];

Sort.Direction direction = parts.length > 1
? Sort.Direction.fromOptionalString(parts[1]).orElse(Sort.Direction.ASC)
: Sort.Direction.ASC;

resolvedSort = Sort.by(direction, property);
}

return PageRequest.of(resolvedPage, resolvedSize, resolvedSort);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package pl.skyroster.skyroster_backend.application.pilot;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import pl.skyroster.skyroster_backend.domain.model.AircraftType;
import pl.skyroster.skyroster_backend.domain.model.Pilot;
import pl.skyroster.skyroster_backend.domain.model.Qualification;
import pl.skyroster.skyroster_backend.domain.port.OperationalBaseRepository;
import pl.skyroster.skyroster_backend.domain.port.PilotRepository;
import pl.skyroster.skyroster_backend.generated.model.PilotPatchRequest;
import pl.skyroster.skyroster_backend.infrastructure.mappers.AircraftTypeInfoMapper;
import pl.skyroster.skyroster_backend.infrastructure.mappers.PilotMapper;
import pl.skyroster.skyroster_backend.generated.model.PilotResponse;
import pl.skyroster.skyroster_backend.infrastructure.mappers.PilotQualificationMapper;

import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class PatchPilotUseCase {
private final PilotRepository pilotRepository;
private final OperationalBaseRepository operationalBaseRepository;

@Transactional
public PilotResponse patchPilot(UUID id, PilotPatchRequest request) {
Pilot pilot = pilotRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Pilot not found: " + id));

String name = request.getName();
if (name != null) {
pilot.setName(name);
}
String surname = request.getSurname();
if (surname != null) {
pilot.setSurname(surname);
}
String icaoCode = request.getOperationalBaseIcaoCode();
if (icaoCode != null) {
pilot.setOperationalBase(
operationalBaseRepository.findByIcaoCode(icaoCode)
.orElseThrow(() -> new IllegalArgumentException("Operational base not found: " + icaoCode))
);
}
Set<Qualification> qualifications = request.getQualifications().stream().map(PilotQualificationMapper::fromPilotQualificationInfo).collect(Collectors.toSet());
pilot.setQualifications(qualifications);

Set<AircraftType> aircraftTypes = request.getAircraftTypes().stream().map(AircraftTypeInfoMapper::toAircraftTypeInfo).collect(Collectors.toSet());
pilot.setAircraftTypes(aircraftTypes);

return PilotMapper.toResponse(pilotRepository.save(pilot));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.skyroster.skyroster_backend.domain.exception;

public class PilotAlreadyExistsException extends RuntimeException {
public PilotAlreadyExistsException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pl.skyroster.skyroster_backend.domain.exception;

import java.util.UUID;

public class PilotNotFoundException extends RuntimeException {
public PilotNotFoundException(UUID pilotId) {
super("Pilot with id " + pilotId + " not found");
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package pl.skyroster.skyroster_backend.domain.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Set;
import java.util.UUID;

import jakarta.persistence.*;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Pilot {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.UUID;

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Qualification {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
import org.springframework.data.jpa.repository.JpaRepository;
import pl.skyroster.skyroster_backend.domain.model.Flight;

import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;

public interface FlightRepository extends JpaRepository<Flight, UUID> {
List<Flight> findAll();
boolean existsByAircraftId(UUID aircraftId);
boolean existsByPilotIdAndFlightEndAfter(
UUID pilotId,
OffsetDateTime now
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package pl.skyroster.skyroster_backend.domain.port;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.Query;
import pl.skyroster.skyroster_backend.domain.model.Pilot;

import java.util.Optional;
import java.util.UUID;

public interface PilotRepository {
@EntityGraph(attributePaths = {
"qualifications",
"aircraftTypes",
"operationalBase"
})
@Query("SELECT p FROM Pilot p")
Page<Pilot> findAllWithRelations(Pageable pageable);
Optional<Pilot> findById(UUID id);
boolean existsById(UUID id);
boolean existsByLicence(String licence);
void deleteById(UUID id);
Pilot save(Pilot pilot);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pl.skyroster.skyroster_backend.infrastructure.adapter.in.web;

import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import pl.skyroster.skyroster_backend.application.pilot.AddPilotUseCase;
import pl.skyroster.skyroster_backend.application.pilot.DeletePilotUseCase;
import pl.skyroster.skyroster_backend.application.pilot.GetPilotUseCase;
import pl.skyroster.skyroster_backend.application.pilot.PatchPilotUseCase;
import pl.skyroster.skyroster_backend.generated.api.ApiApi;
import pl.skyroster.skyroster_backend.generated.model.PagedPilotResponse;
import pl.skyroster.skyroster_backend.generated.model.PilotPatchRequest;
import pl.skyroster.skyroster_backend.generated.model.PilotRequest;
import pl.skyroster.skyroster_backend.generated.model.PilotResponse;

import java.util.UUID;

@RequiredArgsConstructor
@RestController
public class PilotController {

private final GetPilotUseCase getPilotUseCase;
private final DeletePilotUseCase deletePilotUseCase;
private final PatchPilotUseCase patchPilotUseCase;
private final AddPilotUseCase addPilotUseCase;

@GetMapping(ApiApi.PATH_GET_PILOTS)
public ResponseEntity<PagedPilotResponse> getPilots(@RequestParam Integer page, @RequestParam Integer size, @RequestParam @Nullable String sort) {
return ResponseEntity.ok(getPilotUseCase.getPilots(page, size, sort));
}

@DeleteMapping(ApiApi.PATH_DELETE_PILOT_BY_ID)
public ResponseEntity<Void> deletePilotById(@PathVariable UUID pilotId) {
deletePilotUseCase.deletePilotById(pilotId);
return ResponseEntity.noContent().build();
}

@PatchMapping(ApiApi.PATH_PATCH_PILOT)
public ResponseEntity<PilotResponse> patchPilot(@PathVariable UUID pilotId, @RequestBody PilotPatchRequest request) {
return ResponseEntity.ok(patchPilotUseCase.patchPilot(pilotId, request));
}

@PostMapping(ApiApi.PATH_CREATE_PILOT)
public ResponseEntity<PilotResponse> addPilot(@RequestBody PilotRequest pilot){
return ResponseEntity.ok(addPilotUseCase.addPilot(pilot));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pl.skyroster.skyroster_backend.infrastructure.adapter.out.persistence;

import org.springframework.data.jpa.repository.JpaRepository;
import pl.skyroster.skyroster_backend.domain.model.Pilot;
import pl.skyroster.skyroster_backend.domain.port.PilotRepository;

import java.util.UUID;

public interface JpaPilotRepository extends JpaRepository<Pilot, UUID>, PilotRepository {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pl.skyroster.skyroster_backend.domain.exception.AircraftAlreadyExistsException;
import pl.skyroster.skyroster_backend.domain.exception.AircraftHasAssignedFlightsException;
import pl.skyroster.skyroster_backend.domain.exception.AircraftNotFoundException;
import pl.skyroster.skyroster_backend.domain.exception.AircraftTypeNotFoundException;
import pl.skyroster.skyroster_backend.domain.exception.OperationalBaseNotFoundException;
import pl.skyroster.skyroster_backend.domain.exception.*;
import pl.skyroster.skyroster_backend.generated.model.ErrorResponse;

import java.time.OffsetDateTime;
Expand Down Expand Up @@ -50,6 +46,16 @@ public ResponseEntity<ErrorResponse> handleAircraftTypeNotFound(AircraftTypeNotF
return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), request);
}

@ExceptionHandler(PilotNotFoundException.class)
public ResponseEntity<ErrorResponse> handlePilotNotFound(PilotNotFoundException ex,HttpServletRequest request) {
return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), request);
}

@ExceptionHandler(PilotAlreadyExistsException.class)
public ResponseEntity<ErrorResponse> handlePilotAlreadyExists(PilotAlreadyExistsException ex,HttpServletRequest request) {
return buildResponse(HttpStatus.CONFLICT, ex.getMessage(), request);
}

@ExceptionHandler(OperationalBaseNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOperationalBaseNotFound(OperationalBaseNotFoundException ex,
HttpServletRequest request) {
Expand All @@ -62,6 +68,11 @@ public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentExcept
return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), request);
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<ErrorResponse> handleIllegalState(Exception ex, HttpServletRequest request) {
return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), request);
}

private ResponseEntity<ErrorResponse> buildResponse(HttpStatus status, String message,
HttpServletRequest request) {
ErrorResponse error = new ErrorResponse()
Expand Down
Loading
Loading