== Implementation of user scenarios in the prototype In the technical prototype of the application implemented so far, the following scenarios have been implemented: ||= ID =||||= !UseCase =|| ||1||||'''[wiki:SearchRoutesUseCase Search Routes]'''|| ||2||||'''[wiki:OrganizeTripUseCase Organize Trip]'''|| ||3||||'''[wiki:ManageTripUseCase Manage Trip]'''|| ||4||||'''[wiki:ViewMyTripsUseCase View My Trips]'''|| ||5||||'''[wiki:LoginUseCase Login]'''|| ||6||||'''[wiki:RegisterUseCase Register]'''|| === **ID 1** - Search Routes The `/search-routes` endpoint provides users with the ability to search for available transport routes from different organizers. It supports both **viewing all routes** by default and **filtering routes** based on user input (departure and arrival locations). The request is handled by `UserRouteController`, which interacts with `RouteService` to retrieve the relevant data. Here is an example of a search query: -`/search-routes?from=sk&to=ohrid` === {{{ @RequestMapping("/search-routes") public class UserRouteController { private final RouteService routeService; public UserRouteController(RouteService routeService) { this.routeService = routeService; } @GetMapping public String findRoutesByFromAndTo(@RequestParam(required = false) String from, @RequestParam(required = false) String to, Model model) { List filteredRoutes = ((from == null || from.isBlank()) && (to == null || to.isBlank())) ? routeService.findAll() : routeService.findRouteByFromAndToDest(from, to); model.addAttribute(filteredRoutes.isEmpty() ? "noRoutesMessage" : "routes", filteredRoutes.isEmpty() ? "No routes found for your search." : filteredRoutes); model.addAttribute("display", "user/search-routes"); return "master"; } } }}} === ==== Controller Details: - **URL Mapping:** `/search-routes` - **Method:** `GET` - **Functionality:** - Displays **all routes** when the page is accessed without search criteria. - Filters results based on the user's input (`from` and `to`). - **View:** Uses the `master` template and dynamically embeds `user/search-routes`. ==== Breakdown: - When users first visit `/search-routes`, the controller calls `routeService.findAll()` to display all available routes. - When users provide search criteria (`from` and/or `to`), it filters results using `routeService.findRouteByFromAndToDest(from, to)`. - If no matching routes are found, a **"No routes found for your search."** message is displayed instead of an empty table. - The retrieved data is added to the `Model`, ensuring the front-end can render the appropriate content. === ==== Result of `/search-routes` [[Image(search-routes.png, 100%)]] === ==== Result of `/search-routes?from=sk&to=ohrid` [[Image(search-routes-filter.png, 100%)]] === === **ID 2** - Organize Trip The `/routes` endpoint provides transport organizers with the ability to manage their routes and organize trips. It supports **viewing authorized routes**, **viewing trips for a specific route**, and **adding new trips**. The request is handled by `CompanyRouteController` and `CompanyTripController`, which interact with `CompanyRouteService` and `CompanyTripService` to manage routes and trips. Here is an example of the flow to organize a trip: - View routes: `/routes/company` - View trips for a specific route: `/routes/company/view-trips/{routeId}` - Add a new trip: `/routes/company/view-trips/{routeId}/add-trip` === {{{ @RequestMapping("/routes") public class CompanyRouteController { private final CompanyRouteService companyRouteService; public CompanyRouteController(CompanyRouteService companyRouteService) { this.companyRouteService = companyRouteService; } @GetMapping("/company") public String routes(Model model) { model.addAttribute("companyRoutes", companyRouteService.getAuthorizedRoutes()); model.addAttribute("display", "/company/company-route"); return "master"; } } }}} === {{{ @RequestMapping("/routes/company/view-trips/{routeId}") public class CompanyTripController { private final CompanyTripService companyTripService; private final RouteService routeService; private final LocationService locationService; public CompanyTripController(CompanyTripService companyTripService, RouteService routeService, LocationService locationService) { this.companyTripService = companyTripService; this.routeService = routeService; this.locationService = locationService; } @GetMapping public String routeTrips(@PathVariable Integer routeId, Model model) { Route route = routeService.findById(routeId); model.addAttribute("trips", companyTripService.getAuthorizedTripsByRoute(routeId)); model.addAttribute("routeId", routeId); model.addAttribute("locations", locationService.findAll()); model.addAttribute("routeSource", route.getSource()); model.addAttribute("routeDestination", route.getDestination()); model.addAttribute("display", "/company/company-view-trip"); return "master"; } @PostMapping("/add-trip") public String addNewTrip(@PathVariable Integer routeId, @RequestParam("date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, @RequestParam("freeSeats") int freeSeats, @RequestParam("locations") List locationIds, @RequestParam("etas") @DateTimeFormat(pattern = "HH:mm") List etas, RedirectAttributes redirectAttributes) { try { Route route = routeService.findById(routeId); companyTripService.createTrip(route, date, freeSeats, locationIds, etas); redirectAttributes.addFlashAttribute("message", "Trip created successfully!"); } catch (IllegalArgumentException | SecurityException e) { redirectAttributes.addFlashAttribute("error", e.getMessage()); } return "redirect:/routes/company/view-trips/" + routeId; } } }}} === ==== Controller Details: - **URL Mapping:** `/routes` - **Method:** `GET`, `POST` - **Functionality:** - **GET `/routes/company`**: Displays **authorized routes** for the transport organizer. - **GET `/routes/company/view-trips/{routeId}`**: Displays all **authorized trips** for the selected route. - **POST `/routes/company/view-trips/{routeId}/add-trip`**: Allows the transport organizer to **add a new trip** to an existing route. - **View:** Uses the `master` template and dynamically embeds `/company/company-route` and `/company/company-view-trip`. ==== Breakdown: - When the transport organizer accesses `/routes/company`, the controller retrieves all authorized routes using `companyRouteService.getAuthorizedRoutes()` and displays them. - When the transport organizer accesses `/routes/company/view-trips/{routeId}`, the controller retrieves all authorized trips for the specified route using `companyTripService.getAuthorizedTripsByRoute(routeId)` and displays them along with the locations. {{{ /** * Returns every trip for specific route if user is authorized. * * @param routeId the routeId for getting authorized trips * @return all trips for the route || SecurityException("Unauthorized to access these trips.") */ public List getAuthorizedTripsByRoute(Integer routeId) { Integer transportOrganizerId = authorizationService.getAuthenticatedTransportOrganizerId(); List trips = tripService.findAllByPredicate(TripSpecification.tripsByRoute(routeId)); if (!trips.isEmpty() && !trips.get(0).getTranOrg().getTranOrgId().equals(transportOrganizerId)) { throw new SecurityException("Unauthorized to access these trips."); } return trips; } }}} - The transport organizer can add a new trip by submitting the form with details like date, free seats, locations, and estimated times of arrival (ETAs). === ==== Result of `/routes/company` [[Image(company-routes-view.png, 100%)]] === ==== Result of `/routes/company/view-trips/{routeId}` [[Image(company-trips-view.png, 100%)]] === === Result of `/routes/company/view-trips/{routeId}/add-trip` [[Image(add-trip-modal.png, 100%)]] [[Image(add-trip-success.png, 100%)]] ===