From 921b30db5b8991778be62d43fd5ed7f84064a94b Mon Sep 17 00:00:00 2001 From: ivamach Date: Thu, 2 May 2024 18:36:22 +0200 Subject: [PATCH 01/10] SMA-105: added placeName to the RequestEventDTO --- .../src/main/java/com/sportsmatch/dtos/RequestEventDTO.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java index 563a2179..40ea6841 100644 --- a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java @@ -19,4 +19,5 @@ public class RequestEventDTO { private List sportsName = new ArrayList<>(); private double longitude; private double latitude; + private String placeName; } From 5b83439bf71c79f0a267be9f8ed41d9cb6ecc970 Mon Sep 17 00:00:00 2001 From: ivamach Date: Thu, 2 May 2024 18:38:30 +0200 Subject: [PATCH 02/10] SMA-105: added placeName and now to the findNearbyEvents query --- .../repositories/EventRepository.java | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java index d933af22..435394a2 100644 --- a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java @@ -31,39 +31,47 @@ public interface EventRepository extends JpaRepository { List findEventsByUser( @Param("id") Long id, @Param("now") LocalDateTime now, Pageable pageable); - @Query("SELECT ep.event FROM EventPlayer ep WHERE ep.player.id = :id AND ep.event.dateStart > :now ORDER BY ep.event.dateEnd ASC") + @Query( + "SELECT ep.event FROM EventPlayer ep WHERE ep.player.id = :id AND ep.event.dateStart > :now ORDER BY ep.event.dateEnd ASC") List findUpcomingEventsByUser(@Param("id") Long id, @Param("now") LocalDateTime now); - - - /** - *

Finds events near the user location, optionally filtered by sport names.

+ * Finds events near the user location, optionally filtered by sport names. * - *

This method uses a native SQL query with a Haversine distance calculation to find events ordered by their distance from the user's location.

+ *

This method uses a native SQL query with a Haversine distance calculation to find events + * ordered by their distance from the user's location. * * @param userLongitude user's longitude coordinate - * @param userLatitude user's latitude coordinate - * @param sportNames optional, list of sport names to filter events by. - * If null, all events are returned. - * Sport names are not case sensitive. - * @param pageable contains the page and size - * @return list of events filtered by sport names if given, and order by distance from the user's given location + * @param userLatitude user's latitude coordinate + * @param sportNames optional, list of sport names to filter events by. If null, all events are + * returned. Sport names are not case-sensitive. + * @param placeName optional, substring to search name of the place by. If null all places are + * returned. It is not case-sensitive. + * @param now date and time to search events by. Only events starting after this date and time are + * returned. + * @param pageable contains the page and size + * @return list of events filtered by sport names if given, and order by distance from the user's + * given location */ - @Query(nativeQuery = true, value = - "SELECT e.id, e.date_start, e.date_end, e.min_elo, e.max_elo, e.title, e.is_rank_updated, e.sport_id, e.place_id " - + "FROM events e " - + "JOIN sports s ON e.sport_id = s.id " - + "JOIN places p ON e.place_id = p.id " - + "WHERE (:sportNames IS NULL OR LOWER(s.name) IN(:sportNames)) " - + "ORDER BY ( " - + " 6371 * acos( " // Haversine distance calculation - + " cos(radians(p.latitude)) * cos(radians(:latitude)) * " - + " cos(radians(:longitude) - radians(p.longitude)) + " - + " sin(radians(p.latitude)) * sin(radians(:latitude))) " - + " ) ASC;") - List findNearbyEvents(@Param("longitude") final double userLongitude, - @Param("latitude") final double userLatitude, - @Param("sportNames") final List sportNames, - Pageable pageable); + @Query( + nativeQuery = true, + value = + "SELECT e.id, e.date_start, e.date_end, e.min_elo, e.max_elo, e.title, e.is_rank_updated, e.sport_id, e.place_id " + + "FROM events e " + + "JOIN sports s ON e.sport_id = s.id " + + "JOIN places p ON e.place_id = p.id " + + "WHERE (LOWER(p.name) LIKE %:placeName%) AND (:sportNames IS NULL OR LOWER(s.name) IN(:sportNames)) AND (e.date_start > :now)" + + "ORDER BY ( " + + " 6371 * acos( " // Haversine distance calculation + + " cos(radians(p.latitude)) * cos(radians(:latitude)) * " + + " cos(radians(:longitude) - radians(p.longitude)) + " + + " sin(radians(p.latitude)) * sin(radians(:latitude))) " + + " ) ASC;") + List findNearbyEvents( + @Param("longitude") final double userLongitude, + @Param("latitude") final double userLatitude, + @Param("sportNames") final List sportNames, + @Param("placeName") final String placeName, + @Param("now") final LocalDateTime now, + Pageable pageable); } From 1b4e34534843a227100c2fa262888510fb46d028 Mon Sep 17 00:00:00 2001 From: ivamach Date: Thu, 2 May 2024 18:40:13 +0200 Subject: [PATCH 03/10] SMA-105: nearby events can be now searched by place name and only future events are returned --- .../src/main/java/com/sportsmatch/services/EventService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java index 5b73e7b6..54f3083f 100644 --- a/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java @@ -203,6 +203,10 @@ public List getNearbyEvents(RequestEventDTO requestEventDTO, final Pag requestEventDTO.getLongitude(), requestEventDTO.getLatitude(), sportNamesWithLowerCase, + requestEventDTO.getPlaceName() == null + ? "" + : requestEventDTO.getPlaceName().toLowerCase(), + LocalDateTime.now(), pageable); return events.stream().map(eventMapper::convertEventToEventDTO).collect(Collectors.toList()); From 78c4ef6464cb24bd5224b0bf2cec1327ba7ca929 Mon Sep 17 00:00:00 2001 From: ivamach Date: Thu, 2 May 2024 18:41:45 +0200 Subject: [PATCH 04/10] SMA-105: updated generated api client files --- .../src/generated/api/services/EventsControllerService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/sportsmatch-app/src/generated/api/services/EventsControllerService.ts b/frontend/sportsmatch-app/src/generated/api/services/EventsControllerService.ts index f2f27009..e60f125f 100644 --- a/frontend/sportsmatch-app/src/generated/api/services/EventsControllerService.ts +++ b/frontend/sportsmatch-app/src/generated/api/services/EventsControllerService.ts @@ -102,6 +102,7 @@ export class EventsControllerService { * @param sportsName * @param longitude * @param latitude + * @param placeName * @param page Zero-based page index (0..N) * @param size The size of the page to be returned * @param sort Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. @@ -112,6 +113,7 @@ export class EventsControllerService { sportsName?: Array, longitude?: number, latitude?: number, + placeName?: string, page?: number, size: number = 20, sort?: Array, @@ -123,6 +125,7 @@ export class EventsControllerService { 'sportsName': sportsName, 'longitude': longitude, 'latitude': latitude, + 'placeName': placeName, 'page': page, 'size': size, 'sort': sort, From 6a0fecf560547ae5353be371d3fb8c4eb55a86ee Mon Sep 17 00:00:00 2001 From: ivamach Date: Thu, 2 May 2024 18:48:26 +0200 Subject: [PATCH 05/10] SMA-105: updated HostEventComponent to changes in RequetEventDTO --- frontend/sportsmatch-app/src/components/HostEventComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/sportsmatch-app/src/components/HostEventComponent.tsx b/frontend/sportsmatch-app/src/components/HostEventComponent.tsx index 0fc55b1b..213ea0e0 100644 --- a/frontend/sportsmatch-app/src/components/HostEventComponent.tsx +++ b/frontend/sportsmatch-app/src/components/HostEventComponent.tsx @@ -116,6 +116,7 @@ function HostEventComponent() { undefined, undefined, undefined, + undefined, 999, ) if (!Array.isArray(response)) { From d213d4bb6574dc38aff9745b9ea0d49e30329fc9 Mon Sep 17 00:00:00 2001 From: ivamach Date: Thu, 2 May 2024 18:49:28 +0200 Subject: [PATCH 06/10] SMA-105: places are now search by input from the searchbar --- frontend/sportsmatch-app/src/pages/Index.tsx | 27 ++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/frontend/sportsmatch-app/src/pages/Index.tsx b/frontend/sportsmatch-app/src/pages/Index.tsx index 21035610..6849cbd1 100644 --- a/frontend/sportsmatch-app/src/pages/Index.tsx +++ b/frontend/sportsmatch-app/src/pages/Index.tsx @@ -47,6 +47,7 @@ export default function MainPage() { selectedSports, 0, 0, + searchQuery, page, size, ) @@ -70,7 +71,7 @@ export default function MainPage() { } // call the method fetchData() - }, [selectedSports, page]) + }, [selectedSports, page, searchQuery]) // handle join event pop up after cliking on the event const handleEventSelection = (e: EventDTO) => { @@ -140,21 +141,15 @@ export default function MainPage() { {filteredEvent.length === 0 ? ( ) : ( - filteredEvent - .filter((e) => - e.placeDTO.name - .toLowerCase() - .includes(searchQuery.toLowerCase()), - ) - .map((event, index) => ( -

handleEventSelection(event)} - > - -
- )) + filteredEvent.map((event, index) => ( +
handleEventSelection(event)} + > + +
+ )) )} From bed61eca604cfe60595a8f38c9758f41dc52c97d Mon Sep 17 00:00:00 2001 From: ivamach Date: Fri, 3 May 2024 13:24:30 +0200 Subject: [PATCH 07/10] SMA-107: added useLocation hook --- .../sportsmatch-app/src/hooks/UseLocation.tsx | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 frontend/sportsmatch-app/src/hooks/UseLocation.tsx diff --git a/frontend/sportsmatch-app/src/hooks/UseLocation.tsx b/frontend/sportsmatch-app/src/hooks/UseLocation.tsx new file mode 100644 index 00000000..e7aabb80 --- /dev/null +++ b/frontend/sportsmatch-app/src/hooks/UseLocation.tsx @@ -0,0 +1,92 @@ +import { useEffect, useState } from 'react' + +// source https://gist.github.com/KristofferEriksson/ee5af0a52e1fea0acc028e9b10aa0969 + +interface LocationOptions { + enableHighAccuracy?: boolean + timeout?: number + maximumAge?: number +} + +interface LocationState { + coords: { + latitude: number | null + longitude: number | null + accuracy: number | null + altitude: number | null + altitudeAccuracy: number | null + heading: number | null + speed: number | null + } + locatedAt: number | null + error: string | null +} + +const useLocation = (options: LocationOptions = {}) => { + const [location, setLocation] = useState({ + coords: { + latitude: null, + longitude: null, + accuracy: null, + altitude: null, + altitudeAccuracy: null, + heading: null, + speed: null, + }, + locatedAt: null, + error: null, + }) + + useEffect(() => { + // Ensuring this runs only in a client-side environment + if (typeof window === 'undefined' || !('geolocation' in navigator)) { + setLocation((prevState) => ({ + ...prevState, + error: + 'Geolocation is not supported by your browser or not available in the current environment', + })) + return + } + + const handleSuccess = (position: GeolocationPosition) => { + setLocation({ + coords: { + latitude: position.coords.latitude, + longitude: position.coords.longitude, + accuracy: position.coords.accuracy, + altitude: position.coords.altitude, + altitudeAccuracy: position.coords.altitudeAccuracy, + heading: position.coords.heading, + speed: position.coords.speed, + }, + locatedAt: position.timestamp, + error: null, + }) + } + + const handleError = (error: GeolocationPositionError) => { + setLocation((prevState) => ({ + ...prevState, + error: error.message, + })) + } + + const geoOptions = { + enableHighAccuracy: options.enableHighAccuracy || false, + timeout: options.timeout || Infinity, + maximumAge: options.maximumAge || 0, + } + + const watcher = navigator.geolocation.watchPosition( + handleSuccess, + handleError, + geoOptions, + ) + + return () => navigator.geolocation.clearWatch(watcher) + }, [options.enableHighAccuracy, options.timeout, options.maximumAge]) + + return location +} + +export default useLocation From dd7ce84a5f963fb0e73bdca9c607c0234fc18dea Mon Sep 17 00:00:00 2001 From: ivamach Date: Fri, 3 May 2024 13:28:18 +0200 Subject: [PATCH 08/10] SMA-107: user's coordinates are passed when calling getNearbyEvents --- frontend/sportsmatch-app/src/pages/Index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/sportsmatch-app/src/pages/Index.tsx b/frontend/sportsmatch-app/src/pages/Index.tsx index 6849cbd1..e9a7d9f5 100644 --- a/frontend/sportsmatch-app/src/pages/Index.tsx +++ b/frontend/sportsmatch-app/src/pages/Index.tsx @@ -9,6 +9,7 @@ import { ApiError, EventDTO, EventsControllerService } from '../generated/api' import useModal from '../hooks/UseModal' import Modal from '../components/Modal' import JoinEventComponent from '../components/JoinEventComponent' +import useLocation from '../hooks/UseLocation' export default function MainPage() { const [searchQuery, setSearchQuery] = useState('') @@ -21,6 +22,8 @@ export default function MainPage() { const [page, setPage] = useState(0) const size = 3 + const userCoords = useLocation().coords + // handle sports name selected from sportButtoncomponent const handleSportSelectionChange = (selectedButtonSports: string[]) => { setSelectedSports(selectedButtonSports) @@ -45,8 +48,8 @@ export default function MainPage() { try { const response = await EventsControllerService.getNearbyEvents( selectedSports, - 0, - 0, + userCoords.longitude || undefined, + userCoords.latitude || undefined, searchQuery, page, size, @@ -71,7 +74,7 @@ export default function MainPage() { } // call the method fetchData() - }, [selectedSports, page, searchQuery]) + }, [selectedSports, page, searchQuery, userCoords.latitude, userCoords.longitude]) // handle join event pop up after cliking on the event const handleEventSelection = (e: EventDTO) => { From f4bdbbe4e166e694ba17b4e80a45e55ca1d00d51 Mon Sep 17 00:00:00 2001 From: ivamach Date: Fri, 3 May 2024 13:48:57 +0200 Subject: [PATCH 09/10] SMA-107: fixed lint error --- frontend/sportsmatch-app/src/pages/Index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/sportsmatch-app/src/pages/Index.tsx b/frontend/sportsmatch-app/src/pages/Index.tsx index e9a7d9f5..5adea504 100644 --- a/frontend/sportsmatch-app/src/pages/Index.tsx +++ b/frontend/sportsmatch-app/src/pages/Index.tsx @@ -74,7 +74,13 @@ export default function MainPage() { } // call the method fetchData() - }, [selectedSports, page, searchQuery, userCoords.latitude, userCoords.longitude]) + }, [ + selectedSports, + page, + searchQuery, + userCoords.latitude, + userCoords.longitude, + ]) // handle join event pop up after cliking on the event const handleEventSelection = (e: EventDTO) => { From 2cda644fdf34ca41f420c72ce7dc728e22437bd5 Mon Sep 17 00:00:00 2001 From: ivamach Date: Fri, 3 May 2024 13:50:38 +0200 Subject: [PATCH 10/10] SMA-107: fixed int warning --- frontend/sportsmatch-app/src/hooks/UseLocation.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/sportsmatch-app/src/hooks/UseLocation.tsx b/frontend/sportsmatch-app/src/hooks/UseLocation.tsx index e7aabb80..77ab03ff 100644 --- a/frontend/sportsmatch-app/src/hooks/UseLocation.tsx +++ b/frontend/sportsmatch-app/src/hooks/UseLocation.tsx @@ -43,7 +43,8 @@ const useLocation = (options: LocationOptions = {}) => { setLocation((prevState) => ({ ...prevState, error: - 'Geolocation is not supported by your browser or not available in the current environment', + 'Geolocation is not supported by your browser or not available in the current' + + ' environment', })) return }