From 24acc3aa0280199228f3a6e7355037faf7e78662 Mon Sep 17 00:00:00 2001 From: lavatee Date: Thu, 11 Dec 2025 23:37:20 +0700 Subject: [PATCH] api was implemented --- src/main/java/com/example/nto/App.java | 5 + .../nto/controller/BookingController.java | 33 +++++ .../nto/controller/EmployeeController.java | 27 ++++ .../java/com/example/nto/dto/BookRequest.java | 7 + .../com/example/nto/dto/BookingInfoDto.java | 5 + .../example/nto/dto/EmployeeInfoResponse.java | 7 + .../java/com/example/nto/dto/PlaceDto.java | 5 + .../java/com/example/nto/entity/Booking.java | 13 ++ .../java/com/example/nto/entity/Employee.java | 16 ++- .../java/com/example/nto/entity/Place.java | 6 + .../nto/repository/BookingRepository.java | 18 ++- .../nto/repository/EmployeeRepository.java | 9 +- .../nto/repository/PlaceRepository.java | 5 +- .../example/nto/service/BookingService.java | 16 +++ .../example/nto/service/EmployeeService.java | 6 + .../nto/service/impl/BookingServiceImpl.java | 128 ++++++++++++++++++ .../nto/service/impl/EmployeeServiceImpl.java | 22 +++ 17 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/nto/dto/BookRequest.java create mode 100644 src/main/java/com/example/nto/dto/BookingInfoDto.java create mode 100644 src/main/java/com/example/nto/dto/EmployeeInfoResponse.java create mode 100644 src/main/java/com/example/nto/dto/PlaceDto.java diff --git a/src/main/java/com/example/nto/App.java b/src/main/java/com/example/nto/App.java index e453f89..f43e400 100644 --- a/src/main/java/com/example/nto/App.java +++ b/src/main/java/com/example/nto/App.java @@ -6,7 +6,12 @@ package com.example.nto; * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication public class App { public static void main(String[] args) { + SpringApplication.run(App.class, args); } } diff --git a/src/main/java/com/example/nto/controller/BookingController.java b/src/main/java/com/example/nto/controller/BookingController.java index 9885f84..92e0825 100644 --- a/src/main/java/com/example/nto/controller/BookingController.java +++ b/src/main/java/com/example/nto/controller/BookingController.java @@ -1,10 +1,43 @@ package com.example.nto.controller; +import com.example.nto.dto.BookRequest; +import com.example.nto.dto.BookingInfoDto; +import com.example.nto.dto.PlaceDto; +import com.example.nto.service.BookingService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor public class BookingController { + + private final BookingService bookingService; + + @GetMapping("/{code}/booking") + public Map> getAvailable(@PathVariable String code) { + return bookingService.getAvailablePlaces(code); + } + + @PostMapping("/{code}/book") + public ResponseEntity book(@PathVariable String code, @RequestBody BookRequest request) { + System.out.println("place id: " + request.placeId()); + BookingInfoDto booking = bookingService.createBooking(code, request.date(), request.placeId()); + return ResponseEntity.status(201).body(booking); + } } diff --git a/src/main/java/com/example/nto/controller/EmployeeController.java b/src/main/java/com/example/nto/controller/EmployeeController.java index 47658f9..f73323f 100644 --- a/src/main/java/com/example/nto/controller/EmployeeController.java +++ b/src/main/java/com/example/nto/controller/EmployeeController.java @@ -1,10 +1,37 @@ package com.example.nto.controller; +import com.example.nto.dto.EmployeeInfoResponse; +import com.example.nto.service.BookingService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor public class EmployeeController { + + private final BookingService bookingService; + + @GetMapping("/{code}/auth") + public ResponseEntity auth(@PathVariable String code) { + if (bookingService.isCodeValid(code)) { + return ResponseEntity.ok().build(); + } + return ResponseEntity.status(401).build(); + } + + @GetMapping("/{code}/info") + public EmployeeInfoResponse getInfo(@PathVariable String code) { + return bookingService.getEmployeeInfo(code); + } } diff --git a/src/main/java/com/example/nto/dto/BookRequest.java b/src/main/java/com/example/nto/dto/BookRequest.java new file mode 100644 index 0000000..9f9e7fb --- /dev/null +++ b/src/main/java/com/example/nto/dto/BookRequest.java @@ -0,0 +1,7 @@ +package com.example.nto.dto; + +import java.time.LocalDate; + +public record BookRequest(LocalDate date, long placeId) { +} + diff --git a/src/main/java/com/example/nto/dto/BookingInfoDto.java b/src/main/java/com/example/nto/dto/BookingInfoDto.java new file mode 100644 index 0000000..f7da9fa --- /dev/null +++ b/src/main/java/com/example/nto/dto/BookingInfoDto.java @@ -0,0 +1,5 @@ +package com.example.nto.dto; + +public record BookingInfoDto(long id, String place) { +} + diff --git a/src/main/java/com/example/nto/dto/EmployeeInfoResponse.java b/src/main/java/com/example/nto/dto/EmployeeInfoResponse.java new file mode 100644 index 0000000..14ba5a9 --- /dev/null +++ b/src/main/java/com/example/nto/dto/EmployeeInfoResponse.java @@ -0,0 +1,7 @@ +package com.example.nto.dto; + +import java.util.Map; + +public record EmployeeInfoResponse(String name, String photoUrl, Map booking) { +} + diff --git a/src/main/java/com/example/nto/dto/PlaceDto.java b/src/main/java/com/example/nto/dto/PlaceDto.java new file mode 100644 index 0000000..566b838 --- /dev/null +++ b/src/main/java/com/example/nto/dto/PlaceDto.java @@ -0,0 +1,5 @@ +package com.example.nto.dto; + +public record PlaceDto(long id, String place) { +} + diff --git a/src/main/java/com/example/nto/entity/Booking.java b/src/main/java/com/example/nto/entity/Booking.java index 21c1981..16123c5 100644 --- a/src/main/java/com/example/nto/entity/Booking.java +++ b/src/main/java/com/example/nto/entity/Booking.java @@ -1,8 +1,14 @@ package com.example.nto.entity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; import jakarta.persistence.FetchType; +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 lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -21,15 +27,22 @@ import java.time.LocalDate; @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@Table(name = "booking") public class Booking { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + @Column(nullable = false) private LocalDate date; @ManyToOne(targetEntity = Place.class, fetch = FetchType.LAZY) @JoinColumn(name = "place_id") private Place place; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "employee_id") private Employee employee; } diff --git a/src/main/java/com/example/nto/entity/Employee.java b/src/main/java/com/example/nto/entity/Employee.java index a52102b..4d0d554 100644 --- a/src/main/java/com/example/nto/entity/Employee.java +++ b/src/main/java/com/example/nto/entity/Employee.java @@ -1,6 +1,14 @@ package com.example.nto.entity; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -19,14 +27,20 @@ import java.util.List; @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@Table(name = "employee") public class Employee { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; + @Column(unique = true, nullable = false) private String code; + @Column(name = "photo_url") private String photoUrl; @OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY) diff --git a/src/main/java/com/example/nto/entity/Place.java b/src/main/java/com/example/nto/entity/Place.java index 00c253b..4d1fc1d 100644 --- a/src/main/java/com/example/nto/entity/Place.java +++ b/src/main/java/com/example/nto/entity/Place.java @@ -1,8 +1,11 @@ package com.example.nto.entity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -19,11 +22,14 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor +@Entity +@Table(name = "place") public class Place { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + @Column(name = "place_name", nullable = false, unique = true) private String place; } diff --git a/src/main/java/com/example/nto/repository/BookingRepository.java b/src/main/java/com/example/nto/repository/BookingRepository.java index 303bb54..06c4d28 100644 --- a/src/main/java/com/example/nto/repository/BookingRepository.java +++ b/src/main/java/com/example/nto/repository/BookingRepository.java @@ -1,10 +1,26 @@ package com.example.nto.repository; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Employee; +import com.example.nto.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface BookingRepository { +public interface BookingRepository extends JpaRepository { + List findAllByEmployee(Employee employee); + + List findAllByDate(LocalDate date); + + Optional findByDateAndPlace(LocalDate date, Place place); + + Optional findByEmployeeAndDate(Employee employee, LocalDate date); } diff --git a/src/main/java/com/example/nto/repository/EmployeeRepository.java b/src/main/java/com/example/nto/repository/EmployeeRepository.java index 210d29c..628e3de 100644 --- a/src/main/java/com/example/nto/repository/EmployeeRepository.java +++ b/src/main/java/com/example/nto/repository/EmployeeRepository.java @@ -1,10 +1,17 @@ package com.example.nto.repository; +import com.example.nto.entity.Employee; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface EmployeeRepository { +public interface EmployeeRepository extends JpaRepository { + Optional findByCode(String code); + boolean existsByCode(String code); } diff --git a/src/main/java/com/example/nto/repository/PlaceRepository.java b/src/main/java/com/example/nto/repository/PlaceRepository.java index d3bea1d..aff1a7c 100644 --- a/src/main/java/com/example/nto/repository/PlaceRepository.java +++ b/src/main/java/com/example/nto/repository/PlaceRepository.java @@ -1,10 +1,13 @@ package com.example.nto.repository; +import com.example.nto.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ -public interface PlaceRepository { +public interface PlaceRepository extends JpaRepository { } diff --git a/src/main/java/com/example/nto/service/BookingService.java b/src/main/java/com/example/nto/service/BookingService.java index 31ec148..096b64c 100644 --- a/src/main/java/com/example/nto/service/BookingService.java +++ b/src/main/java/com/example/nto/service/BookingService.java @@ -1,5 +1,13 @@ package com.example.nto.service; +import com.example.nto.dto.BookingInfoDto; +import com.example.nto.dto.EmployeeInfoResponse; +import com.example.nto.dto.PlaceDto; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= @@ -7,4 +15,12 @@ package com.example.nto.service; * НЕЛЬЗЯ: Изменять название класса и пакета */ public interface BookingService { + + boolean isCodeValid(String code); + + EmployeeInfoResponse getEmployeeInfo(String code); + + Map> getAvailablePlaces(String code); + + BookingInfoDto createBooking(String code, LocalDate date, long placeId); } diff --git a/src/main/java/com/example/nto/service/EmployeeService.java b/src/main/java/com/example/nto/service/EmployeeService.java index cccd209..eca69ca 100644 --- a/src/main/java/com/example/nto/service/EmployeeService.java +++ b/src/main/java/com/example/nto/service/EmployeeService.java @@ -1,5 +1,7 @@ package com.example.nto.service; +import com.example.nto.entity.Employee; + /** * TODO: ДОРАБОТАТЬ в рамках задания * ================================= @@ -7,4 +9,8 @@ package com.example.nto.service; * НЕЛЬЗЯ: Изменять название класса и пакета */ public interface EmployeeService { + + boolean existsByCode(String code); + + Employee getByCode(String code); } diff --git a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java index d24b244..84ed14a 100644 --- a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java +++ b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java @@ -1,6 +1,31 @@ package com.example.nto.service.impl; +import com.example.nto.dto.BookingInfoDto; +import com.example.nto.dto.EmployeeInfoResponse; +import com.example.nto.dto.PlaceDto; +import com.example.nto.entity.Booking; +import com.example.nto.entity.Employee; +import com.example.nto.entity.Place; +import com.example.nto.repository.BookingRepository; +import com.example.nto.repository.EmployeeRepository; +import com.example.nto.repository.PlaceRepository; import com.example.nto.service.BookingService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CONFLICT; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; /** * TODO: ДОРАБОТАТЬ в рамках задания @@ -8,5 +33,108 @@ import com.example.nto.service.BookingService; * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@Service +@RequiredArgsConstructor public class BookingServiceImpl implements BookingService { + + private final EmployeeRepository employeeRepository; + private final BookingRepository bookingRepository; + private final PlaceRepository placeRepository; + + @Value("${booking.days-ahead:3}") + private int daysAhead; + + @Override + public boolean isCodeValid(String code) { + return employeeRepository.existsByCode(code); + } + + @Override + public EmployeeInfoResponse getEmployeeInfo(String code) { + Employee employee = employeeRepository.findByCode(code) + .orElseThrow(() -> new ResponseStatusException(UNAUTHORIZED)); + + Map bookingMap = bookingRepository.findAllByEmployee(employee) + .stream() + .collect(Collectors.toMap( + b -> b.getDate().toString(), + b -> new BookingInfoDto(b.getId(), b.getPlace().getPlace()), + (first, second) -> first + )); + + return new EmployeeInfoResponse(employee.getName(), employee.getPhotoUrl(), bookingMap); + } + + @Override + public Map> getAvailablePlaces(String code) { + ensureCodeValid(code); + + List allPlaces = placeRepository.findAll(); + Map> result = new HashMap<>(); + + for (int i = 0; i <= daysAhead; i++) { + LocalDate date = LocalDate.now().plusDays(i); + Set bookedPlaceIds = bookingRepository.findAllByDate(date) + .stream() + .map(booking -> booking.getPlace().getId()) + .collect(Collectors.toSet()); + + List available = allPlaces.stream() + .filter(place -> !bookedPlaceIds.contains(place.getId())) + .map(place -> new PlaceDto(place.getId(), place.getPlace())) + .toList(); + + result.put(date.toString(), available); + } + + return result; + } + + @Override + @Transactional + public BookingInfoDto createBooking(String code, LocalDate date, long placeId) { + Employee employee = employeeRepository.findByCode(code) + .orElseThrow(() -> new ResponseStatusException(UNAUTHORIZED)); + + validateDate(date); + + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new ResponseStatusException(BAD_REQUEST)); + + bookingRepository.findByEmployeeAndDate(employee, date) + .ifPresent(existing -> { + throw new ResponseStatusException(CONFLICT); + }); + + bookingRepository.findByDateAndPlace(date, place) + .ifPresent(existing -> { + throw new ResponseStatusException(CONFLICT); + }); + + Booking booking = Booking.builder() + .date(date) + .place(place) + .employee(employee) + .build(); + + Booking saved = bookingRepository.save(booking); + return new BookingInfoDto(saved.getId(), saved.getPlace().getPlace()); + } + + private void ensureCodeValid(String code) { + if (!employeeRepository.existsByCode(code)) { + throw new ResponseStatusException(UNAUTHORIZED); + } + } + + private void validateDate(LocalDate date) { + if (date == null) { + throw new ResponseStatusException(BAD_REQUEST); + } + LocalDate today = LocalDate.now(); + LocalDate lastAvailable = today.plusDays(daysAhead); + if (date.isBefore(today) || date.isAfter(lastAvailable)) { + throw new ResponseStatusException(BAD_REQUEST); + } + } } diff --git a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java index f8125e5..360cf8e 100644 --- a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java +++ b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java @@ -1,6 +1,13 @@ package com.example.nto.service.impl; +import com.example.nto.entity.Employee; +import com.example.nto.repository.EmployeeRepository; import com.example.nto.service.EmployeeService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import static org.springframework.http.HttpStatus.UNAUTHORIZED; /** * TODO: ДОРАБОТАТЬ в рамках задания @@ -8,5 +15,20 @@ import com.example.nto.service.EmployeeService; * МОЖНО: Добавлять методы, аннотации, зависимости * НЕЛЬЗЯ: Изменять название класса и пакета */ +@Service +@RequiredArgsConstructor public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + + @Override + public boolean existsByCode(String code) { + return employeeRepository.existsByCode(code); + } + + @Override + public Employee getByCode(String code) { + return employeeRepository.findByCode(code) + .orElseThrow(() -> new ResponseStatusException(UNAUTHORIZED)); + } }