package application import ( "encoding/json" "net/http" "time" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification" groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage" mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage" savedsearchtypes "git.coopgo.io/coopgo-platform/saved-search/data/types" "github.com/gorilla/mux" "github.com/paulmach/orb/geojson" "github.com/rs/zerolog/log" ) func (h *Handler) JourneysSearchHTTPHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { log.Error().Err(err).Msg("error parsing form") http.Error(w, "Bad Request", http.StatusBadRequest) return } // Extract and convert HTTP parameters departureDate := r.FormValue("departuredate") departureTime := r.FormValue("departuretime") departure := r.FormValue("departure") destination := r.FormValue("destination") passengerID := r.FormValue("passengerid") solidarityTransportExcludeDriver := r.FormValue("solidarity_transport_exclude_driver") // Parse timezone and datetime locTime, err := time.LoadLocation("Europe/Paris") if err != nil { log.Error().Err(err).Msg("timezone error") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } var departureDateTime time.Time if departureDate != "" && departureTime != "" { departureDateTime, err = time.ParseInLocation("2006-01-02 15:04", departureDate+" "+departureTime, locTime) if err != nil { log.Error().Err(err).Msg("error parsing datetime") http.Error(w, "Bad Request", http.StatusBadRequest) return } log.Info(). Str("departureDate", departureDate). Str("departureTime", departureTime). Time("departureDateTime", departureDateTime). Str("timezone", departureDateTime.Location().String()). Str("RFC3339", departureDateTime.Format(time.RFC3339)). Msg("Journey search - parsed departure datetime") } // Parse departure location var departureGeo *geojson.Feature if departure == "" && passengerID != "" { // Get passenger address p, err := h.services.GetAccount(passengerID) if err != nil { log.Error().Err(err).Msg("could not retrieve passenger") http.Error(w, "Not Found", http.StatusNotFound) return } departureBytes, err := json.Marshal(p.Data["address"]) if err != nil { log.Error().Err(err).Any("address", p.Data["address"]).Msg("could not marshal address") } else { departureGeo, err = geojson.UnmarshalFeature(departureBytes) if err != nil { log.Error().Err(err).Msg("error unmarshalling passenger departure") http.Error(w, "Bad Request", http.StatusBadRequest) return } } } else if departure != "" { departureGeo, err = geojson.UnmarshalFeature([]byte(departure)) if err != nil { log.Error().Err(err).Msg("error unmarshalling departure") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Parse destination location var destinationGeo *geojson.Feature if destination != "" { destinationGeo, err = geojson.UnmarshalFeature([]byte(destination)) if err != nil { log.Error().Err(err).Msg("error unmarshalling destination") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Call business logic handler result, err := h.applicationHandler.SearchJourneys( r.Context(), departureDateTime, departureGeo, destinationGeo, passengerID, solidarityTransportExcludeDriver, ) if err != nil { log.Error().Err(err).Msg("error in journey search") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Get beneficiaries for rendering g := r.Context().Value(identification.GroupKey) if g == nil { log.Error().Msg("group not found in request context") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } group := g.(groupstorage.Group) beneficiaries, err := h.services.GetBeneficiariesInGroup(group) if err != nil { log.Error().Err(err).Msg("issue retrieving beneficiaries") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Get saved searches for this group when no search has been performed var savedSearches []*savedsearchtypes.SavedSearch var beneficiariesMap map[string]mobilityaccountsstorage.Account if !result.Searched { savedSearches, err = h.applicationHandler.GetSavedSearchesByOwner(r.Context(), group.ID) if err != nil { log.Error().Err(err).Msg("issue retrieving saved searches") // Don't fail the request, just log the error savedSearches = []*savedsearchtypes.SavedSearch{} } // Create beneficiaries map for template lookup beneficiariesMap = make(map[string]mobilityaccountsstorage.Account) for _, b := range beneficiaries { beneficiariesMap[b.ID] = b } } // Render HTTP response h.renderer.JourneysSearch( w, r, result.CarpoolResults, result.TransitResults, result.VehicleResults, result.Searched, departureGeo, destinationGeo, departureDate, departureTime, result.DriverJourneys, result.Drivers, result.OrganizedCarpools, beneficiaries, result.KnowledgeBaseResults, passengerID, savedSearches, beneficiariesMap, ) } } func (h *Handler) SaveSearchHTTPHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { log.Error().Err(err).Msg("error parsing form") http.Error(w, "Bad Request", http.StatusBadRequest) return } // Extract search parameters from URL query query := r.URL.Query() departureDate := query.Get("departuredate") departureTime := query.Get("departuretime") departure := query.Get("departure") destination := query.Get("destination") passengerID := query.Get("passengerid") // Debug logging log.Debug(). Str("departuredate", departureDate). Str("departuretime", departureTime). Str("departure", departure). Str("destination", destination). Str("passengerid", passengerID). Str("query", r.URL.RawQuery). Msg("SaveSearch request parameters") // Parse timezone and datetime locTime, err := time.LoadLocation("Europe/Paris") if err != nil { log.Error().Err(err).Msg("timezone error") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } var departureDateTime time.Time if departureDate != "" && departureTime != "" { departureDateTime, err = time.ParseInLocation("2006-01-02 15:04", departureDate+" "+departureTime, locTime) if err != nil { log.Error().Err(err).Msg("error parsing datetime") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Parse departure location var departureGeo *geojson.Feature if departure != "" { departureGeo, err = geojson.UnmarshalFeature([]byte(departure)) if err != nil { log.Error().Err(err).Msg("error unmarshalling departure") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Parse destination location var destinationGeo *geojson.Feature if destination != "" { destinationGeo, err = geojson.UnmarshalFeature([]byte(destination)) if err != nil { log.Error().Err(err).Msg("error unmarshalling destination") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Get group ID from session/context g := r.Context().Value(identification.GroupKey) if g == nil { log.Error().Msg("group not found in request context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } group := g.(groupstorage.Group) // Prepare additional data additionalData := map[string]interface{}{} if passengerID != "" { additionalData["passenger_id"] = passengerID } // Save the search using the business logic err = h.applicationHandler.SaveSearch( r.Context(), group.ID, departureDateTime, departureGeo, destinationGeo, additionalData, ) if err != nil { log.Error().Err(err).Msg("error saving search") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Redirect back to search with success message http.Redirect(w, r, "/app/journeys/", http.StatusSeeOther) } } func (h *Handler) DeleteSavedSearchHTTPHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Get search ID from URL parameters vars := mux.Vars(r) searchID := vars["id"] if searchID == "" { log.Error().Msg("search ID not provided") http.Error(w, "Bad Request", http.StatusBadRequest) return } // Get group ID from session/context for authorization g := r.Context().Value(identification.GroupKey) if g == nil { log.Error().Msg("group not found in request context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } group := g.(groupstorage.Group) // Delete the saved search using the business logic err := h.applicationHandler.DeleteSavedSearch(r.Context(), searchID, group.ID) if err != nil { log.Error().Err(err).Str("search_id", searchID).Msg("error deleting saved search") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } log.Info().Str("search_id", searchID).Str("group_id", group.ID).Msg("saved search deleted successfully") // Redirect back to journeys page http.Redirect(w, r, "/app/journeys/?deleted=1", http.StatusSeeOther) } } func (h *Handler) JourneysSearchCompactHTTPHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { log.Error().Err(err).Msg("error parsing form") http.Error(w, "Bad Request", http.StatusBadRequest) return } // Extract and convert HTTP parameters (same as JourneysSearchHTTPHandler) departureDate := r.FormValue("departuredate") departureTime := r.FormValue("departuretime") departure := r.FormValue("departure") destination := r.FormValue("destination") passengerID := r.FormValue("passengerid") solidarityTransportExcludeDriver := r.FormValue("solidarity_transport_exclude_driver") // Parse timezone and datetime locTime, err := time.LoadLocation("Europe/Paris") if err != nil { log.Error().Err(err).Msg("timezone error") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } var departureDateTime time.Time if departureDate != "" && departureTime != "" { departureDateTime, err = time.ParseInLocation("2006-01-02 15:04", departureDate+" "+departureTime, locTime) if err != nil { log.Error().Err(err).Msg("error parsing datetime") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Parse departure location var departureGeo *geojson.Feature if departure == "" && passengerID != "" { // Get passenger address p, err := h.services.GetAccount(passengerID) if err != nil { log.Error().Err(err).Msg("could not retrieve passenger") http.Error(w, "Not Found", http.StatusNotFound) return } departureBytes, err := json.Marshal(p.Data["address"]) if err != nil { log.Error().Err(err).Any("address", p.Data["address"]).Msg("could not marshal address") } else { departureGeo, err = geojson.UnmarshalFeature(departureBytes) if err != nil { log.Error().Err(err).Msg("error unmarshalling passenger departure") http.Error(w, "Bad Request", http.StatusBadRequest) return } } } else if departure != "" { departureGeo, err = geojson.UnmarshalFeature([]byte(departure)) if err != nil { log.Error().Err(err).Msg("error unmarshalling departure") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Parse destination location var destinationGeo *geojson.Feature if destination != "" { destinationGeo, err = geojson.UnmarshalFeature([]byte(destination)) if err != nil { log.Error().Err(err).Msg("error unmarshalling destination") http.Error(w, "Bad Request", http.StatusBadRequest) return } } // Call business logic handler result, err := h.applicationHandler.SearchJourneys( r.Context(), departureDateTime, departureGeo, destinationGeo, passengerID, solidarityTransportExcludeDriver, ) if err != nil { log.Error().Err(err).Msg("error in journey search") http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Render compact HTTP response h.renderer.JourneysSearchCompact( w, r, result.CarpoolResults, result.TransitResults, result.VehicleResults, result.Searched, departureGeo, destinationGeo, departureDate, departureTime, result.DriverJourneys, result.Drivers, result.OrganizedCarpools, result.KnowledgeBaseResults, passengerID, ) } }