Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bb2de4c
Merge branch 'main' of https://github.com/Script-languages/skyroster
kudukm May 6, 2026
7e1a227
feat(SKY-101): add Rules GET API, entity, repository, use-case, contr…
Sonetic May 9, 2026
ad008ce
test(SKY-101): enable RulesController integration test (requires Dock…
Sonetic May 9, 2026
a7663a3
fix(SKY-101): avoid duplicate RuleRepository beans by not extending d…
Sonetic May 9, 2026
e7b3ea9
Merge branch 'main' of https://github.com/Script-languages/skyroster
kudukm May 9, 2026
2dde417
Merge branch 'main' of https://github.com/Script-languages/skyroster
kudukm May 10, 2026
6ea5788
Merge branch 'main' of https://github.com/Script-languages/skyroster
kudukm May 10, 2026
2606470
endpoint na get
Sonetic May 10, 2026
96d7744
(SKY-41) /api/flights endpoint adapted to OpenAPI
kudukm May 10, 2026
5762975
(SKY-41) Unused imports deleted
kudukm May 10, 2026
07cfadd
Merge branch 'main' into sky-25
Sonetic May 10, 2026
4c98d06
Merge pull request #15 from Script-languages/SKY-41-timetable-openapi
Krywion May 12, 2026
28c45fc
[SKY-26]: Add API GET endpoint for all pilots
Axan18 May 9, 2026
1eee3c6
[SKY-28]: Add API DELETE endpoint for pilot
Axan18 May 10, 2026
87a45c6
[SKY-29]: Add API PATCH endpoint for pilots
Axan18 May 14, 2026
6f9e921
[SKY-20]: Add API POST endpoint for pilots
Axan18 May 14, 2026
79cdd99
Merge pull request #12 from Script-languages/feature/pilots-api
Krywion May 15, 2026
864c874
feat(SKY-25): add OpenAPI spec for GET /api/rules
Krywion May 15, 2026
fb5f98e
feat(SKY-25): add Rule domain model and repository port
Krywion May 15, 2026
b41bf41
feat(SKY-25): add rules table migration with seed data
Krywion May 15, 2026
310ec1e
feat(SKY-25): add JPA adapter for RuleRepository
Krywion May 15, 2026
72194a1
feat(SKY-25): add GetRulesUseCase
Krywion May 15, 2026
69eb324
feat(SKY-25): add RuleController with GET /api/rules
Krywion May 15, 2026
822e64f
test(SKY-25): add integration tests for GET /api/rules
Krywion May 15, 2026
faf6b6c
merge(SKY-25): integrate origin/sky-25 keeping our implementation
Krywion May 15, 2026
b8f7334
revert(SKY-25): drop docker-compose Keycloak DB split from SKY-101 merge
Krywion May 15, 2026
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,8 +2,10 @@

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pl.skyroster.skyroster_backend.domain.model.Flight;
import pl.skyroster.skyroster_backend.domain.port.FlightRepository;
import pl.skyroster.skyroster_backend.generated.model.FlightResponse;
import pl.skyroster.skyroster_backend.infrastructure.mappers.AircraftResponseMapper;
import pl.skyroster.skyroster_backend.infrastructure.mappers.OperationalBaseInfoMapper;

import java.util.List;

Expand All @@ -16,7 +18,19 @@ public GetFlightsUseCase(FlightRepository flightRepository) {
}

@Transactional(readOnly = true)
public List<Flight> execute() {
return flightRepository.findAll();
public List<FlightResponse> execute() {
return flightRepository.findAll().stream().map(flight -> {
var aircraftResponse = AircraftResponseMapper.map(flight.getAircraft());

return new FlightResponse(
flight.getId(),
aircraftResponse,
flight.getFlightStart(),
flight.getFlightEnd(),
OperationalBaseInfoMapper.map(flight.getStartAirport()),
OperationalBaseInfoMapper.map(flight.getEndAirport()),
flight.getDescription()
);
}).toList();
}
}
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,23 @@
package pl.skyroster.skyroster_backend.application.rule;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pl.skyroster.skyroster_backend.domain.model.Rule;
import pl.skyroster.skyroster_backend.domain.port.RuleRepository;

import java.util.List;

@Service
public class GetRulesUseCase {

private final RuleRepository ruleRepository;

public GetRulesUseCase(RuleRepository ruleRepository) {
this.ruleRepository = ruleRepository;
}

@Transactional(readOnly = true)
public List<Rule> execute() {
return ruleRepository.findAll();
}
}
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
@@ -0,0 +1,41 @@
package pl.skyroster.skyroster_backend.domain.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.UUID;

@Entity
@Table(name = "rules")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Rule {

@Id
private UUID id;

@Column(nullable = false, length = 100)
private String name;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
private RuleType type;

@Column(nullable = false)
private Integer value;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private RulePeriod period;

@Column(length = 500)
private String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.skyroster.skyroster_backend.domain.model;

public enum RulePeriod {
DAY,
WEEK,
MONTH
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.skyroster.skyroster_backend.domain.model;

public enum RuleType {
MAX_WORK_TIME,
MIN_REST_TIME,
MIN_FLIGHT_TIME
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package pl.skyroster.skyroster_backend.domain.port;

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 {
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);
}
Loading
Loading