Compare commits

3 Commits
main ... main

Author SHA1 Message Date
Vladimir
fcadb35360 2
Some checks failed
Android Test / validate-and-test (pull_request) Has been cancelled
2025-11-29 15:50:47 +07:00
Vladimir
4593d56158 1 2025-11-29 15:49:04 +07:00
a6954c2013 Update .gitea/workflows/workflow.yml 2025-11-24 17:17:06 +00:00
15 changed files with 373 additions and 35 deletions

View File

@@ -23,7 +23,7 @@ jobs:
GITEA_HEAD_REF: ${{ gitea.event.pull_request.head.ref }}
- name: Checkout tests
run: python3 /opt/scripts/copy-tests.py --repo-url "Olympic/NTO-2025-Android-TeamTask-tests" --branch "main" --task-type "spring"
run: python3 /opt/scripts/copy-tests.py --repo-url "Olympic/NTO-2025-Backend-TeamTask-tests" --branch "main" --task-type "spring"
- name: Run tests
run: mvn test

View File

@@ -1,12 +1,16 @@
package com.example.nto;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

View File

@@ -0,0 +1,34 @@
package com.example.nto;
import com.example.nto.exception.AlreadyBookedException;
import com.example.nto.exception.CodeNotFoundException;
import com.example.nto.exception.InvalidRequestException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(InvalidRequestException.class)
public ResponseEntity<String> handleInvalidRequest(InvalidRequestException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
}
@ExceptionHandler(CodeNotFoundException.class)
public ResponseEntity<String> handleCodeNotFound(CodeNotFoundException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
}
@ExceptionHandler(AlreadyBookedException.class)
public ResponseEntity<String> handleAlreadyBooked(AlreadyBookedException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleJsonParseError(HttpMessageNotReadableException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("что-то пошло не так");
}
}

View File

@@ -1,10 +1,113 @@
package com.example.nto.controller;
import com.example.nto.entity.Booking;
import com.example.nto.entity.Employee;
import com.example.nto.entity.Place;
import com.example.nto.exception.AlreadyBookedException;
import com.example.nto.exception.CodeNotFoundException;
import com.example.nto.exception.InvalidRequestException;
import com.example.nto.repository.BookingRepository;
import com.example.nto.repository.EmployeeRepository;
import com.example.nto.repository.PlaceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
/**
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@RestController
@RequestMapping("/api/{code}")
public class BookingController {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private PlaceRepository placeRepository;
@Autowired
private BookingRepository bookingRepository;
static class BookingRequest {
public LocalDate date;
public Long placeID;
}
@GetMapping("/booking")
public ResponseEntity<?> getAvailablePlaces(@PathVariable String code) {
Employee employee = employeeRepository.findByCode(code);
if (code == null || code.trim().isEmpty()) {
return ResponseEntity
.badRequest()
.body("что-то пошло не так");
}
if (employeeRepository.findByCode(code.trim()) == null) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body("кода не существует");
}
LocalDate today = LocalDate.now();
Map<String, List<Map<String, Object>>> result = new LinkedHashMap<>();
for (int i = 0; i <= 3; i++) {
LocalDate date = today.plusDays(i);
String dateKey = date.toString();
List<Place> allPlaces = placeRepository.findAll();
List<Map<String, Object>> freePlaces = new ArrayList<>();
for (Place place : allPlaces) {
if (!bookingRepository.existsByDateAndPlace_Id(date, place.getId())) {
Map<String, Object> placeInfo = new LinkedHashMap<>();
placeInfo.put("id", place.getId());
placeInfo.put("place", place.getPlaceName());
freePlaces.add(placeInfo);
}
}
result.put(dateKey, freePlaces);
}
return ResponseEntity.ok(result);
}
@PostMapping("/book")
public ResponseEntity<String> book(@PathVariable String code, @RequestBody BookingRequest req) {
if (req == null || req.date == null || req.placeID == null) {
throw new InvalidRequestException();
}
Employee employee = employeeRepository.findByCode(code.trim());
if (employee == null) {
throw new CodeNotFoundException();
}
Place place = placeRepository.findById(req.placeID).orElse(null);
if (place == null) {
throw new InvalidRequestException();
}
if (bookingRepository.existsByDateAndPlace_Id(req.date, req.placeID)) {
throw new AlreadyBookedException();
}
Booking booking = new Booking();
booking.setDate(req.date);
booking.setPlace(place);
booking.setEmployee(employee);
bookingRepository.save(booking);
return ResponseEntity
.status(HttpStatus.CREATED)
.body("бронирование успешно создано");
}
}

View File

@@ -1,10 +1,87 @@
package com.example.nto.controller;
import com.example.nto.entity.Booking;
import com.example.nto.entity.Employee;
import com.example.nto.exception.CodeNotFoundException;
import com.example.nto.exception.InvalidRequestException;
import com.example.nto.repository.BookingRepository;
import com.example.nto.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@RestController
@RequestMapping("/api/{code}")
public class EmployeeController {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private BookingRepository bookingRepository;
@GetMapping("/auth")
public ResponseEntity<?> checkAuth(@PathVariable String code) {
if (code == null || code.trim().isEmpty()) {
throw new InvalidRequestException();
}
if (employeeRepository.findByCode(code.trim()) == null) {
throw new CodeNotFoundException();
}
return ResponseEntity.status(200).body("данный код существует - можно пользоваться приложением");
}
@GetMapping("/info")
public ResponseEntity<?> getInfo(@PathVariable String code) {
if (code == null || code.trim().isEmpty()) {
throw new InvalidRequestException();
}
if (employeeRepository.findByCode(code.trim()) == null) {
throw new CodeNotFoundException();
}
String cleanCode = code.trim();
Employee employee = employeeRepository.findByCode(cleanCode);
if (employee == null) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body("кода не существует");
}
List<Booking> bookings = bookingRepository.findByEmployee_Code(code);
Map<String, Object> response = getStringObjectMap(employee, bookings);
return ResponseEntity.ok(response);
}
private Map<String, Object> getStringObjectMap(Employee employee, List<Booking> bookings) {
Map<String, Object> response = new LinkedHashMap<>();
response.put("name", employee.getName());
response.put("photoUrl", employee.getPhotoUrl());
Map<String, Object> bookingMap = new HashMap<>();
for (Booking b : bookings) {
String dateStr = b.getDate().toString();
Map<String, Object> placeInfo = new HashMap<>();
placeInfo.put("id", b.getPlace().getId());
placeInfo.put("place", b.getPlace().getPlaceName());
bookingMap.put(dateStr, placeInfo);
}
response.put("booking", bookingMap);
return response;
}
}

View File

@@ -1,12 +1,7 @@
package com.example.nto.entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDate;
@@ -17,19 +12,54 @@ import java.time.LocalDate;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@Data
@Builder
@Entity
@Table(name = "booking")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Booking {
public void setId(Long id) {
this.id = id;
}
private long id;
public void setDate(LocalDate date) {
this.date = date;
}
public void setPlace(Place place) {
this.place = place;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public LocalDate getDate() {
return date;
}
public Place getPlace() {
return place;
}
public Employee getEmployee() {
return employee;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "date", nullable = false)
private LocalDate date;
@ManyToOne(targetEntity = Place.class, fetch = FetchType.LAZY)
@JoinColumn(name = "place_id")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "place_id", nullable = false)
private Place place;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "employee_id", nullable = false)
private Employee employee;
}

View File

@@ -1,10 +1,7 @@
package com.example.nto.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.*;
import java.util.List;
@@ -15,20 +12,59 @@ import java.util.List;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@Data
@Builder
@Entity
@Table(name = "employee")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "code", nullable = false, unique = true)
private String code;
@Column(name = "photo_url")
private String photoUrl;
@OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Booking> bookingList;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getCode() {
return code;
}
public String getPhotoUrl() {
return photoUrl;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCode(String code) {
this.code = code;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
}

View File

@@ -1,12 +1,7 @@
package com.example.nto.entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import lombok.*;
/**
@@ -15,15 +10,29 @@ import lombok.NoArgsConstructor;
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "place")
public class Place {
public Long getId() {
return id;
}
@Column(name = "place_name", nullable = false, unique = true)
public String getPlaceName() {
return place;
}
public void setPlaceName(String place) {
this.place = place;
}
public void setId(Long id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private Long id;
@Column(name = "place_name", nullable = false, unique = true)
private String place;
}

View File

@@ -0,0 +1,7 @@
package com.example.nto.exception;
public class AlreadyBookedException extends RuntimeException {
public AlreadyBookedException() {
super("уже забронировано");
}
}

View File

@@ -0,0 +1,7 @@
package com.example.nto.exception;
public class CodeNotFoundException extends RuntimeException {
public CodeNotFoundException() {
super("кода не существует");
}
}

View File

@@ -0,0 +1,7 @@
package com.example.nto.exception;
public class InvalidRequestException extends RuntimeException {
public InvalidRequestException() {
super("что-то пошло не так");
}
}

View File

@@ -1,10 +1,21 @@
package com.example.nto.repository;
import com.example.nto.entity.Booking;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
/**
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
public interface BookingRepository {
@Repository
public interface BookingRepository extends JpaRepository<Booking, Long> {
List<Booking> findByEmployee_Code(String code);
List<Booking> findByDate(LocalDate date);
boolean existsByDateAndPlace_Id(LocalDate date, Long placeId);
}

View File

@@ -1,10 +1,16 @@
package com.example.nto.repository;
import com.example.nto.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
public interface EmployeeRepository {
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Employee findByCode(String code);
}

View File

@@ -1,10 +1,16 @@
package com.example.nto.repository;
import com.example.nto.entity.Place;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* TODO: ДОРАБОТАТЬ в рамках задания
* =================================
* МОЖНО: Добавлять методы, аннотации, зависимости
* НЕЛЬЗЯ: Изменять название класса и пакета
*/
public interface PlaceRepository {
@Repository
public interface PlaceRepository extends JpaRepository<Place, Long> {
}

View File

@@ -8,3 +8,4 @@ package com.example.nto.service;
*/
public interface BookingService {
}