diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java index 1c77e55..e7c26fe 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java @@ -16,6 +16,7 @@ public class AdminLocationController { private final LocationService locationService; + private static final String LOC_ID_PATH = "/{locationId}"; @PostMapping @ResponseStatus(HttpStatus.CREATED) @@ -28,14 +29,20 @@ public List getAllLocations() { return locationService.getAllLocations(); } - @GetMapping("/{locationId}") + @GetMapping(LOC_ID_PATH) public LocationDto getLocation(@PathVariable Long locationId) { return locationService.getLocation(locationId); } - @DeleteMapping("/{locationId}") + @PatchMapping(LOC_ID_PATH) + public LocationDto updateLocation(@PathVariable Long locationId, + @Valid @RequestBody NewLocationDto locationDto) { + return locationService.updateLocation(locationId, locationDto); + } + + @DeleteMapping(LOC_ID_PATH) @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteLocation(@PathVariable Long locationId) { locationService.deleteLocation(locationId); } -} \ No newline at end of file +} diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java index cca0008..fa806ae 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java @@ -5,6 +5,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import ru.practicum.ewm.dto.response.EventShortDto; import ru.practicum.ewm.dto.response.LocationDto; import ru.practicum.ewm.service.LocationService; @@ -26,4 +27,9 @@ public List getAllLocations() { public LocationDto getLocation(@PathVariable Long locationId) { return locationService.getLocation(locationId); } -} \ No newline at end of file + + @GetMapping("/{locationId}/events") + public List getEventsInLocation(@PathVariable Long locationId) { + return locationService.getEventsInLocation(locationId); + } +} diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java index 9d03cb1..298479c 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java @@ -28,4 +28,5 @@ public class Location { @Column Float radius; + } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index 464f914..cb1925d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -52,5 +52,13 @@ List findPublishedEvents( @Param("rangeEnd") LocalDateTime rangeEnd, @Param("onlyAvailable") Boolean onlyAvailable, Pageable pageable); + + @Query(value = "SELECT e.* FROM events e " + + "JOIN locations l ON e.location_id = l.id " + + "WHERE distance(l.lat, l.lon, :targetLat, :targetLon) <= :targetRadius " + + "AND e.state = 'PUBLISHED'", nativeQuery = true) + List findEventsNearby(@Param("targetLat") float lat, + @Param("targetLon") float lon, + @Param("targetRadius") float radius); Optional findByIdAndState(Long id, EventState state); } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java index 05f12f7..c1e0de0 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java @@ -1,6 +1,7 @@ package ru.practicum.ewm.service; import ru.practicum.ewm.dto.request.NewLocationDto; +import ru.practicum.ewm.dto.response.EventShortDto; import ru.practicum.ewm.dto.response.LocationDto; import java.util.List; @@ -14,4 +15,8 @@ public interface LocationService { List getAllLocations(); void deleteLocation(Long locationId); + + List getEventsInLocation(Long locationId); + + LocationDto updateLocation(Long locationId, NewLocationDto dto); } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java index e1464f4..3455719 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java @@ -6,9 +6,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.ewm.dto.request.NewLocationDto; +import ru.practicum.ewm.dto.response.EventShortDto; import ru.practicum.ewm.dto.response.LocationDto; +import ru.practicum.ewm.exception.NotFoundException; +import ru.practicum.ewm.mapper.EventMapper; import ru.practicum.ewm.mapper.LocationMapper; import ru.practicum.ewm.model.Location; +import ru.practicum.ewm.repository.EventRepository; import ru.practicum.ewm.repository.LocationRepository; import java.util.List; @@ -21,18 +25,19 @@ public class LocationServiceImpl implements LocationService { LocationRepository locationRepository; + EventRepository eventRepository; @Override @Transactional public LocationDto createLocation(NewLocationDto newLocationDto) { Location location = LocationMapper.toLocation(newLocationDto); - Location savedLocation = locationRepository.save(location); - return LocationMapper.toDto(savedLocation); + return LocationMapper.toDto(locationRepository.save(location)); } + @Override public LocationDto getLocation(Long locationId) { Location location = locationRepository.findById(locationId) - .orElseThrow(() -> new RuntimeException("Location not found")); + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); return LocationMapper.toDto(location); } @@ -46,6 +51,34 @@ public List getAllLocations() { @Override @Transactional public void deleteLocation(Long locationId) { + if (!locationRepository.existsById(locationId)) { + throw new NotFoundException("Location with id=" + locationId + " was not found"); + } locationRepository.deleteById(locationId); } + + @Override + public List getEventsInLocation(Long locationId) { + Location location = locationRepository.findById(locationId) + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); + + return eventRepository.findEventsNearby(location.getLat(), location.getLon(), location.getRadius()) + .stream() + .map(EventMapper::toShortDto) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public LocationDto updateLocation(Long locationId, NewLocationDto dto) { + Location location = locationRepository.findById(locationId) + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); + + if (dto.getName() != null && !dto.getName().isBlank()) location.setName(dto.getName()); + if (dto.getLat() != null) location.setLat(dto.getLat()); + if (dto.getLon() != null) location.setLon(dto.getLon()); + if (dto.getRadius() != null) location.setRadius(dto.getRadius()); + + return LocationMapper.toDto(locationRepository.save(location)); + } } \ No newline at end of file diff --git a/ewm-main-service/src/main/resources/application.properties b/ewm-main-service/src/main/resources/application.properties index daedddc..bc3b1de 100644 --- a/ewm-main-service/src/main/resources/application.properties +++ b/ewm-main-service/src/main/resources/application.properties @@ -3,7 +3,7 @@ server.port=8080 spring.datasource.url=jdbc:postgresql://localhost:5432/ewm spring.datasource.username=postgres spring.datasource.password=postgres -spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect - +spring.sql.init.mode=always +spring.jpa.hibernate.ddl-auto=update stats.server.url=http://localhost:9090 \ No newline at end of file diff --git a/ewm-main-service/src/main/resources/schema.sql b/ewm-main-service/src/main/resources/schema.sql new file mode 100644 index 0000000..f137553 --- /dev/null +++ b/ewm-main-service/src/main/resources/schema.sql @@ -0,0 +1,32 @@ +CREATE OR REPLACE FUNCTION distance(lat1 float, lon1 float, lat2 float, lon2 float) + RETURNS float +AS +' +declare + dist float = 0; + rad_lat1 float; + rad_lat2 float; + theta float; + rad_theta float; +BEGIN + IF lat1 = lat2 AND lon1 = lon2 + THEN + RETURN dist; + ELSE + rad_lat1 = pi() * lat1 / 180; + rad_lat2 = pi() * lat2 / 180; + theta = lon1 - lon2; + rad_theta = pi() * theta / 180; + dist = sin(rad_lat1) * sin(rad_lat2) + cos(rad_lat1) * cos(rad_lat2) * cos(rad_theta); + + IF dist > 1 THEN dist = 1; END IF; + + dist = acos(dist); + dist = dist * 180 / pi(); + dist = dist * 60 * 1.8524; + + RETURN dist; + END IF; +END; +' +LANGUAGE PLPGSQL; diff --git a/postman/feature.json b/postman/feature.json new file mode 100644 index 0000000..544ead0 --- /dev/null +++ b/postman/feature.json @@ -0,0 +1,178 @@ +{ + "info": { + "_postman_id": "a70aa31c-9893-4c9e-af88-b5f4f554393b", + "name": "Location-Final", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "51668874" + }, + "item": [ + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"name\": \"Dasha\", \"email\": \"dasha@test.com\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/users", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"name\": \"Концерты\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/categories", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"Центр\",\r\n \"lat\": 55.75,\r\n \"lon\": 37.61,\r\n \"radius\": 5.0\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/locations", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "locations" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"annotation\": \"Очень крутой концерт в самом центре города\",\r\n \"category\": 1,\r\n \"description\": \"Длинное описание события более двадцати символов\",\r\n \"eventDate\": \"2026-12-31 20:00:00\",\r\n \"location\": { \"lat\": 55.751, \"lon\": 37.611 },\r\n \"paid\": false,\r\n \"participantLimit\": 0,\r\n \"requestModeration\": false,\r\n \"title\": \"Супер Концерт\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/users/1/events", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users", + "1", + "events" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"stateAction\": \"PUBLISH_EVENT\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/events/1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "events", + "1" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/locations/1/events", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "locations", + "1", + "events" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file