Compare commits
2 Commits
52de8d363e
...
b1323867c7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1323867c7 | ||
|
|
6cda7509c1 |
@@ -23,6 +23,11 @@ func ReadConfig() (*viper.Viper, error) {
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"listen": "0.0.0.0:8081",
|
"listen": "0.0.0.0:8081",
|
||||||
},
|
},
|
||||||
|
"publicweb": map[string]any{
|
||||||
|
"enabled": false,
|
||||||
|
"listen": "0.0.0.0:8082",
|
||||||
|
"root_dir": "public_themes/default",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"identification": map[string]any{
|
"identification": map[string]any{
|
||||||
"sessions": map[string]any{
|
"sessions": map[string]any{
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DashboardResult struct {
|
type DashboardResult struct {
|
||||||
Accounts []mobilityaccountsstorage.Account
|
Accounts []mobilityaccountsstorage.Account
|
||||||
Members []mobilityaccountsstorage.Account
|
Members []mobilityaccountsstorage.Account
|
||||||
Events []agendastorage.Event
|
Events []agendastorage.Event
|
||||||
Bookings []fleetstorage.Booking
|
Bookings []fleetstorage.Booking
|
||||||
SolidarityDrivers []mobilityaccountsstorage.Account
|
SolidarityDrivers []mobilityaccountsstorage.Account
|
||||||
OrganizedCarpoolDrivers []mobilityaccountsstorage.Account
|
OrganizedCarpoolDrivers []mobilityaccountsstorage.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddressGeoLayer, driverAddressGeoCode string) (*DashboardResult, error) {
|
func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddressGeoLayer, driverAddressGeoCode string) (*DashboardResult, error) {
|
||||||
@@ -62,14 +62,7 @@ func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddress
|
|||||||
|
|
||||||
accounts := []mobilityaccountsstorage.Account{}
|
accounts := []mobilityaccountsstorage.Account{}
|
||||||
|
|
||||||
// We only display the 5 most recent here
|
for _, account := range resp.Accounts {
|
||||||
count := len(resp.Accounts)
|
|
||||||
min := count - 5
|
|
||||||
if min < 0 {
|
|
||||||
min = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range resp.Accounts[min:] {
|
|
||||||
// Check if not archived
|
// Check if not archived
|
||||||
if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived {
|
if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived {
|
||||||
a := account.ToStorageType()
|
a := account.ToStorageType()
|
||||||
@@ -225,4 +218,3 @@ func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddress
|
|||||||
OrganizedCarpoolDrivers: organizedCarpoolDrivers,
|
OrganizedCarpoolDrivers: organizedCarpoolDrivers,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ func (h *ApplicationHandler) GetVehicleDisplay(ctx context.Context, vehicleID st
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID, startdate, enddate, unavailablefrom, unavailableto string) error {
|
func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID string, startdate, enddate *time.Time, unavailablefrom, unavailableto string) error {
|
||||||
booking, err := h.services.GetBooking(bookingID)
|
booking, err := h.services.GetBooking(bookingID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get booking: %w", err)
|
return fmt.Errorf("failed to get booking: %w", err)
|
||||||
@@ -289,21 +289,19 @@ func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID, start
|
|||||||
|
|
||||||
newbooking, _ := fleets.BookingFromStorageType(&booking)
|
newbooking, _ := fleets.BookingFromStorageType(&booking)
|
||||||
|
|
||||||
if startdate != "" {
|
if startdate != nil {
|
||||||
newstartdate, _ := time.Parse("2006-01-02", startdate)
|
newbooking.Startdate = timestamppb.New(*startdate)
|
||||||
newbooking.Startdate = timestamppb.New(newstartdate)
|
|
||||||
|
|
||||||
if newstartdate.Before(newbooking.Unavailablefrom.AsTime()) {
|
if startdate.Before(newbooking.Unavailablefrom.AsTime()) {
|
||||||
newbooking.Unavailablefrom = timestamppb.New(newstartdate)
|
newbooking.Unavailablefrom = timestamppb.New(*startdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if enddate != "" {
|
if enddate != nil {
|
||||||
newenddate, _ := time.Parse("2006-01-02", enddate)
|
newbooking.Enddate = timestamppb.New(*enddate)
|
||||||
newbooking.Enddate = timestamppb.New(newenddate)
|
|
||||||
|
|
||||||
if newenddate.After(newbooking.Unavailableto.AsTime()) || newenddate.Equal(newbooking.Unavailableto.AsTime()) {
|
if enddate.After(newbooking.Unavailableto.AsTime()) || enddate.Equal(newbooking.Unavailableto.AsTime()) {
|
||||||
newbooking.Unavailableto = timestamppb.New(newenddate.Add(24 * time.Hour))
|
newbooking.Unavailableto = timestamppb.New(enddate.Add(24 * time.Hour))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ type VehiclesSearchResult struct {
|
|||||||
BeneficiaryDocuments []filestorage.FileInfo
|
BeneficiaryDocuments []filestorage.FileInfo
|
||||||
Groups map[string]any
|
Groups map[string]any
|
||||||
Searched bool
|
Searched bool
|
||||||
StartDate string
|
StartDate time.Time
|
||||||
EndDate string
|
EndDate time.Time
|
||||||
VehicleType string
|
VehicleType string
|
||||||
Automatic bool
|
Automatic bool
|
||||||
MandatoryDocuments []string
|
MandatoryDocuments []string
|
||||||
@@ -41,22 +41,13 @@ type VehiclesSearchResult struct {
|
|||||||
Beneficiaries []mobilityaccountsstorage.Account
|
Beneficiaries []mobilityaccountsstorage.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID, startDateStr, endDateStr, vehicleType string, automatic bool) (*VehiclesSearchResult, error) {
|
func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID string, startdate, enddate time.Time, vehicleType string, automatic bool) (*VehiclesSearchResult, error) {
|
||||||
var beneficiary mobilityaccountsstorage.Account
|
var beneficiary mobilityaccountsstorage.Account
|
||||||
beneficiarydocuments := []filestorage.FileInfo{}
|
beneficiarydocuments := []filestorage.FileInfo{}
|
||||||
vehicles := []storage.Vehicle{}
|
vehicles := []storage.Vehicle{}
|
||||||
searched := false
|
searched := false
|
||||||
administrators := []string{}
|
administrators := []string{}
|
||||||
|
|
||||||
startdate, err := time.Parse("2006-01-02", startDateStr)
|
|
||||||
if err != nil {
|
|
||||||
startdate = time.Time{}
|
|
||||||
}
|
|
||||||
enddate, err := time.Parse("2006-01-02", endDateStr)
|
|
||||||
if err != nil {
|
|
||||||
enddate = time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if beneficiaryID != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) {
|
if beneficiaryID != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) {
|
||||||
// Handler form
|
// Handler form
|
||||||
searched = true
|
searched = true
|
||||||
@@ -165,8 +156,8 @@ func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID,
|
|||||||
BeneficiaryDocuments: beneficiarydocuments,
|
BeneficiaryDocuments: beneficiarydocuments,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Searched: searched,
|
Searched: searched,
|
||||||
StartDate: startDateStr,
|
StartDate: startdate,
|
||||||
EndDate: endDateStr,
|
EndDate: enddate,
|
||||||
VehicleType: vehicleType,
|
VehicleType: vehicleType,
|
||||||
Automatic: automatic,
|
Automatic: automatic,
|
||||||
MandatoryDocuments: mandatoryDocuments,
|
MandatoryDocuments: mandatoryDocuments,
|
||||||
@@ -180,7 +171,7 @@ type BookVehicleResult struct {
|
|||||||
BookingID string
|
BookingID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, beneficiaryID, startDateStr, endDateStr string, documents map[string]io.Reader, documentHeaders map[string]string, existingDocs map[string]string, currentUserID string, currentUserClaims map[string]any, currentGroup any) (*BookVehicleResult, error) {
|
func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, beneficiaryID string, startdate, enddate time.Time, documents map[string]io.Reader, documentHeaders map[string]string, existingDocs map[string]string, currentUserID string, currentUserClaims map[string]any, currentGroup any) (*BookVehicleResult, error) {
|
||||||
group := currentGroup.(groupsmanagementstorage.Group)
|
group := currentGroup.(groupsmanagementstorage.Group)
|
||||||
|
|
||||||
vehicle, err := h.services.GRPC.Fleets.GetVehicle(ctx, &fleets.GetVehicleRequest{
|
vehicle, err := h.services.GRPC.Fleets.GetVehicle(ctx, &fleets.GetVehicleRequest{
|
||||||
@@ -190,15 +181,6 @@ func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, benefic
|
|||||||
return nil, fmt.Errorf("vehicle not found: %w", err)
|
return nil, fmt.Errorf("vehicle not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
startdate, err := time.Parse("2006-01-02", startDateStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid start date: %w", err)
|
|
||||||
}
|
|
||||||
enddate, err := time.Parse("2006-01-02", endDateStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid end date: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"booked_by": map[string]any{
|
"booked_by": map[string]any{
|
||||||
"user": map[string]any{
|
"user": map[string]any{
|
||||||
|
|||||||
16
main.go
16
main.go
@@ -10,6 +10,7 @@ import (
|
|||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/servers/mcp"
|
"git.coopgo.io/coopgo-apps/parcoursmob/servers/mcp"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/servers/publicweb"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/servers/web"
|
"git.coopgo.io/coopgo-apps/parcoursmob/servers/web"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
@@ -24,9 +25,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dev_env = cfg.GetBool("dev_env")
|
dev_env = cfg.GetBool("dev_env")
|
||||||
webEnabled = cfg.GetBool("server.web.enabled")
|
webEnabled = cfg.GetBool("server.web.enabled")
|
||||||
mcpEnabled = cfg.GetBool("server.mcp.enabled")
|
mcpEnabled = cfg.GetBool("server.mcp.enabled")
|
||||||
|
publicwebEnabled = cfg.GetBool("server.publicweb.enabled")
|
||||||
)
|
)
|
||||||
|
|
||||||
if dev_env {
|
if dev_env {
|
||||||
@@ -80,5 +82,13 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if publicwebEnabled {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
publicweb.Run(cfg, svc, applicationHandler, kv, filestorage)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
142
servers/publicweb/journeys.go
Normal file
142
servers/publicweb/journeys.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package publicweb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/paulmach/orb/geojson"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JourneySearchResponse represents the search results for hydration
|
||||||
|
type JourneySearchResponse struct {
|
||||||
|
Searched bool `json:"searched"`
|
||||||
|
DepartureDate string `json:"departure_date,omitempty"`
|
||||||
|
DepartureTime string `json:"departure_time,omitempty"`
|
||||||
|
Departure any `json:"departure,omitempty"`
|
||||||
|
Destination any `json:"destination,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Results struct {
|
||||||
|
SolidarityDrivers struct {
|
||||||
|
Number int `json:"number"`
|
||||||
|
} `json:"solidarity_drivers"`
|
||||||
|
OrganizedCarpools struct {
|
||||||
|
Number int `json:"number"`
|
||||||
|
} `json:"organized_carpools"`
|
||||||
|
Carpools struct {
|
||||||
|
Number int `json:"number"`
|
||||||
|
Results any `json:"results,omitempty"`
|
||||||
|
} `json:"carpools"`
|
||||||
|
PublicTransit struct {
|
||||||
|
Number int `json:"number"`
|
||||||
|
Results any `json:"results,omitempty"`
|
||||||
|
} `json:"public_transit"`
|
||||||
|
Vehicles struct {
|
||||||
|
Number int `json:"number"`
|
||||||
|
Results any `json:"results,omitempty"`
|
||||||
|
} `json:"vehicles"`
|
||||||
|
LocalSolutions struct {
|
||||||
|
Number int `json:"number"`
|
||||||
|
Results any `json:"results,omitempty"`
|
||||||
|
} `json:"local_solutions"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// journeySearchDataProvider provides data for the journey search page
|
||||||
|
func (s *PublicWebServer) journeySearchDataProvider(r *http.Request) (any, error) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("error parsing form")
|
||||||
|
return JourneySearchResponse{Error: "invalid request"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
departureDate := r.FormValue("departuredate")
|
||||||
|
departureTime := r.FormValue("departuretime")
|
||||||
|
departure := r.FormValue("departure")
|
||||||
|
destination := r.FormValue("destination")
|
||||||
|
|
||||||
|
response := JourneySearchResponse{
|
||||||
|
DepartureDate: departureDate,
|
||||||
|
DepartureTime: departureTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no search parameters, return empty response
|
||||||
|
if departure == "" || destination == "" || departureDate == "" || departureTime == "" {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse timezone and datetime
|
||||||
|
locTime, err := time.LoadLocation("Europe/Paris")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("timezone error")
|
||||||
|
response.Error = "internal error"
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
departureDateTime, err := time.ParseInLocation("2006-01-02 15:04", departureDate+" "+departureTime, locTime)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error parsing datetime")
|
||||||
|
response.Error = "invalid date/time format"
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse departure location
|
||||||
|
departureGeo, err := geojson.UnmarshalFeature([]byte(departure))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error unmarshalling departure")
|
||||||
|
response.Error = "invalid departure location"
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
response.Departure = departureGeo
|
||||||
|
|
||||||
|
// Parse destination location
|
||||||
|
destinationGeo, err := geojson.UnmarshalFeature([]byte(destination))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error unmarshalling destination")
|
||||||
|
response.Error = "invalid destination location"
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
response.Destination = destinationGeo
|
||||||
|
|
||||||
|
// Call business logic
|
||||||
|
result, err := s.applicationHandler.SearchJourneys(
|
||||||
|
r.Context(),
|
||||||
|
departureDateTime,
|
||||||
|
departureGeo,
|
||||||
|
destinationGeo,
|
||||||
|
"", // passengerID
|
||||||
|
"", // solidarityTransportExcludeDriver
|
||||||
|
"", // solidarityExcludeGroupId
|
||||||
|
nil, // options - use defaults
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error in journey search")
|
||||||
|
response.Error = "search failed"
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Searched = result.Searched
|
||||||
|
|
||||||
|
// Solidarity drivers
|
||||||
|
response.Results.SolidarityDrivers.Number = len(result.DriverJourneys)
|
||||||
|
|
||||||
|
// Organized carpools
|
||||||
|
response.Results.OrganizedCarpools.Number = len(result.OrganizedCarpools)
|
||||||
|
|
||||||
|
// Carpools (from external operators like Movici)
|
||||||
|
response.Results.Carpools.Number = len(result.CarpoolResults)
|
||||||
|
response.Results.Carpools.Results = result.CarpoolResults
|
||||||
|
|
||||||
|
// Public transit
|
||||||
|
response.Results.PublicTransit.Number = len(result.TransitResults)
|
||||||
|
response.Results.PublicTransit.Results = result.TransitResults
|
||||||
|
|
||||||
|
// Fleet vehicles
|
||||||
|
response.Results.Vehicles.Number = len(result.VehicleResults)
|
||||||
|
response.Results.Vehicles.Results = result.VehicleResults
|
||||||
|
|
||||||
|
// Knowledge base / local solutions
|
||||||
|
response.Results.LocalSolutions.Number = len(result.KnowledgeBaseResults)
|
||||||
|
response.Results.LocalSolutions.Results = result.KnowledgeBaseResults
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
191
servers/publicweb/publicweb.go
Normal file
191
servers/publicweb/publicweb.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package publicweb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
|
cache "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataProvider returns data to hydrate a page
|
||||||
|
type DataProvider func(r *http.Request) (any, error)
|
||||||
|
|
||||||
|
// DynamicRoute defines a route with its HTML file and data provider
|
||||||
|
type DynamicRoute struct {
|
||||||
|
HTMLFile string
|
||||||
|
DataProvider DataProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex to find the placeholder script tag
|
||||||
|
var dynamicDataRegex = regexp.MustCompile(`<script\s+id="dynamic-data"\s+type="application/json">\s*</script>`)
|
||||||
|
|
||||||
|
type PublicWebServer struct {
|
||||||
|
cfg *viper.Viper
|
||||||
|
services *services.ServicesHandler
|
||||||
|
kv cache.KVHandler
|
||||||
|
filestorage cache.FileStorage
|
||||||
|
applicationHandler *application.ApplicationHandler
|
||||||
|
rootDir string
|
||||||
|
dynamicRoutes map[string]DynamicRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(
|
||||||
|
cfg *viper.Viper,
|
||||||
|
svc *services.ServicesHandler,
|
||||||
|
applicationHandler *application.ApplicationHandler,
|
||||||
|
kv cache.KVHandler,
|
||||||
|
filestorage cache.FileStorage,
|
||||||
|
) {
|
||||||
|
address := cfg.GetString("server.publicweb.listen")
|
||||||
|
rootDir := cfg.GetString("server.publicweb.root_dir")
|
||||||
|
serviceName := cfg.GetString("service_name")
|
||||||
|
|
||||||
|
server := &PublicWebServer{
|
||||||
|
cfg: cfg,
|
||||||
|
services: svc,
|
||||||
|
kv: kv,
|
||||||
|
filestorage: filestorage,
|
||||||
|
applicationHandler: applicationHandler,
|
||||||
|
rootDir: rootDir,
|
||||||
|
dynamicRoutes: make(map[string]DynamicRoute),
|
||||||
|
}
|
||||||
|
|
||||||
|
server.registerDynamicRoutes()
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
r.HandleFunc("/health", server.healthHandler).Methods("GET")
|
||||||
|
|
||||||
|
for pattern := range server.dynamicRoutes {
|
||||||
|
r.HandleFunc(pattern, server.dynamicHandler).Methods("GET", "POST")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.PathPrefix("/").Handler(server.fileServerHandler())
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: address,
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("service_name", serviceName).
|
||||||
|
Str("address", address).
|
||||||
|
Str("root_dir", rootDir).
|
||||||
|
Msg("Running Public Web HTTP server")
|
||||||
|
|
||||||
|
err := srv.ListenAndServe()
|
||||||
|
log.Error().Err(err).Msg("Public Web server error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicWebServer) registerDynamicRoutes() {
|
||||||
|
s.RegisterDynamicRoute("/recherche/", "recherche/index.html", s.journeySearchDataProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicWebServer) RegisterDynamicRoute(pattern, htmlFile string, provider DataProvider) {
|
||||||
|
s.dynamicRoutes[pattern] = DynamicRoute{
|
||||||
|
HTMLFile: htmlFile,
|
||||||
|
DataProvider: provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicWebServer) dynamicHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
pattern, _ := route.GetPathTemplate()
|
||||||
|
|
||||||
|
dynRoute, exists := s.dynamicRoutes[pattern]
|
||||||
|
if !exists {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := dynRoute.DataProvider(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("route", pattern).Msg("Error getting data")
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.hydrate(w, dynRoute.HTMLFile, data); err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hydrate reads an HTML file and injects JSON data into <script id="dynamic-data" type="application/json"></script>
|
||||||
|
func (s *PublicWebServer) hydrate(w http.ResponseWriter, htmlFile string, data any) error {
|
||||||
|
htmlPath := filepath.Join(s.rootDir, htmlFile)
|
||||||
|
htmlContent, err := os.ReadFile(htmlPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("file", htmlPath).Msg("Error reading HTML file")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error marshaling data to JSON")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the placeholder with a script that assigns data to window.__PARCOURSMOB_DATA__
|
||||||
|
replacement := []byte(`<script id="dynamic-data">window.__PARCOURSMOB_DATA__ = ` + string(jsonData) + `;</script>`)
|
||||||
|
modifiedHTML := dynamicDataRegex.ReplaceAll(htmlContent, replacement)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.Write(modifiedHTML)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicWebServer) fileServerHandler() http.Handler {
|
||||||
|
fs := http.FileServer(http.Dir(s.rootDir))
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := filepath.Join(s.rootDir, r.URL.Path)
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
if filepath.Ext(path) == "" {
|
||||||
|
if idx := filepath.Join(path, "index.html"); fileExists(idx) {
|
||||||
|
http.ServeFile(w, r, idx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if idx := filepath.Join(s.rootDir, "index.html"); fileExists(idx) {
|
||||||
|
http.ServeFile(w, r, idx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, _ := os.Stat(path); info != nil && info.IsDir() {
|
||||||
|
if idx := filepath.Join(path, "index.html"); fileExists(idx) && strings.HasSuffix(r.URL.Path, "/") {
|
||||||
|
http.ServeFile(w, r, idx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicWebServer) healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"status":"healthy"}`))
|
||||||
|
}
|
||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
gen "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
gen "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -25,7 +25,7 @@ func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
|
|||||||
// Extract filter parameters using the same field names as the old handler
|
// Extract filter parameters using the same field names as the old handler
|
||||||
tab := r.FormValue("tab")
|
tab := r.FormValue("tab")
|
||||||
if tab == "" {
|
if tab == "" {
|
||||||
tab = "solidarityService" // Default to showing current bookings
|
tab = "solidarityService" // Default to showing current bookings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract archived filter
|
// Extract archived filter
|
||||||
@@ -55,7 +55,7 @@ func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
|
|||||||
|
|
||||||
// History filters (apply when on solidarityHistory tab)
|
// History filters (apply when on solidarityHistory tab)
|
||||||
if tab == "solidarityHistory" {
|
if tab == "solidarityHistory" {
|
||||||
histStatus = r.FormValue("status") // Note: history uses same field names as current
|
histStatus = r.FormValue("status") // Note: history uses same field names as current
|
||||||
histDriverID = r.FormValue("driver_id")
|
histDriverID = r.FormValue("driver_id")
|
||||||
histStartDate = r.FormValue("date_start")
|
histStartDate = r.FormValue("date_start")
|
||||||
histEndDate = r.FormValue("date_end")
|
histEndDate = r.FormValue("date_end")
|
||||||
@@ -135,25 +135,25 @@ func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
|
|||||||
|
|
||||||
// Build filters map for template (matching old handler format)
|
// Build filters map for template (matching old handler format)
|
||||||
filters := map[string]any{
|
filters := map[string]any{
|
||||||
"tab": tab,
|
"tab": tab,
|
||||||
"date_start": startDate,
|
"date_start": startDate,
|
||||||
"date_end": endDate,
|
"date_end": endDate,
|
||||||
"status": status,
|
"status": status,
|
||||||
"driver_id": driverID,
|
"driver_id": driverID,
|
||||||
"departure_geo": departureGeo,
|
"departure_geo": departureGeo,
|
||||||
"destination_geo": destinationGeo,
|
"destination_geo": destinationGeo,
|
||||||
"passenger_address_geo": passengerAddressGeo,
|
"passenger_address_geo": passengerAddressGeo,
|
||||||
"driver_address_geo": driverAddressGeo,
|
"driver_address_geo": driverAddressGeo,
|
||||||
}
|
}
|
||||||
|
|
||||||
histFilters := map[string]any{
|
histFilters := map[string]any{
|
||||||
"tab": tab,
|
"tab": tab,
|
||||||
"date_start": histStartDate,
|
"date_start": histStartDate,
|
||||||
"date_end": histEndDate,
|
"date_end": histEndDate,
|
||||||
"status": histStatus,
|
"status": histStatus,
|
||||||
"driver_id": histDriverID,
|
"driver_id": histDriverID,
|
||||||
"departure_geo": histDepartureGeo,
|
"departure_geo": histDepartureGeo,
|
||||||
"destination_geo": histDestinationGeo,
|
"destination_geo": histDestinationGeo,
|
||||||
"passenger_address_geo": histPassengerAddressGeo,
|
"passenger_address_geo": histPassengerAddressGeo,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +521,11 @@ func (h *Handler) SolidarityTransportDriverJourneyHTTPHandler() http.HandlerFunc
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
driverID := vars["driverid"]
|
driverID := vars["driverid"]
|
||||||
journeyID := vars["journeyid"]
|
journeyID := vars["journeyid"]
|
||||||
|
// Preserve passengerid from either query parameter or form data
|
||||||
passengerID := r.URL.Query().Get("passengerid")
|
passengerID := r.URL.Query().Get("passengerid")
|
||||||
|
if passengerID == "" {
|
||||||
|
passengerID = r.FormValue("passengerid")
|
||||||
|
}
|
||||||
replacesBookingID := r.URL.Query().Get("replaces_booking_id")
|
replacesBookingID := r.URL.Query().Get("replaces_booking_id")
|
||||||
|
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
@@ -580,7 +584,16 @@ func (h *Handler) SolidarityTransportDriverJourneyToggleNoreturnHTTPHandler() ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s/journeys/%s", driverID, journeyID), http.StatusFound)
|
// Preserve passengerid query parameter if present
|
||||||
|
passengerID := r.URL.Query().Get("passengerid")
|
||||||
|
if passengerID == "" {
|
||||||
|
passengerID = r.FormValue("passengerid")
|
||||||
|
}
|
||||||
|
redirectURL := fmt.Sprintf("/app/solidarity-transport/drivers/%s/journeys/%s", driverID, journeyID)
|
||||||
|
if passengerID != "" {
|
||||||
|
redirectURL += "?passengerid=" + passengerID
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, redirectURL, http.StatusFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,11 +654,11 @@ func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFun
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
pricing := map[string]interface{}{
|
pricing := map[string]interface{}{
|
||||||
"passenger": map[string]interface{}{
|
"passenger": map[string]interface{}{
|
||||||
"amount": pricingResult["passenger"].Amount,
|
"amount": pricingResult["passenger"].Amount,
|
||||||
"currency": pricingResult["passenger"].Currency,
|
"currency": pricingResult["passenger"].Currency,
|
||||||
},
|
},
|
||||||
"driver": map[string]interface{}{
|
"driver": map[string]interface{}{
|
||||||
"amount": pricingResult["driver"].Amount,
|
"amount": pricingResult["driver"].Amount,
|
||||||
"currency": pricingResult["driver"].Currency,
|
"currency": pricingResult["driver"].Currency,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -716,7 +729,7 @@ func (h *Handler) SolidarityTransportCreateReplacementBookingHTTPHandler() http.
|
|||||||
journeyID,
|
journeyID,
|
||||||
passengerID,
|
passengerID,
|
||||||
motivation,
|
motivation,
|
||||||
message, // message from form
|
message, // message from form
|
||||||
doNotSend, // doNotSend from form checkbox
|
doNotSend, // doNotSend from form checkbox
|
||||||
returnWaitingTimeMinutes,
|
returnWaitingTimeMinutes,
|
||||||
oldBookingID,
|
oldBookingID,
|
||||||
@@ -771,4 +784,5 @@ func (h *Handler) SolidarityTransportBookingStatusHTTPHandler(action string) htt
|
|||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/bookings/%s", bookingID), http.StatusSeeOther)
|
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/bookings/%s", bookingID), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
@@ -18,11 +19,31 @@ func (h *Handler) VehiclesSearchHTTPHandler() http.HandlerFunc {
|
|||||||
|
|
||||||
// Extract parameters
|
// Extract parameters
|
||||||
beneficiaryID := r.FormValue("beneficiaryid")
|
beneficiaryID := r.FormValue("beneficiaryid")
|
||||||
startDate := r.FormValue("startdate")
|
startDateStr := r.FormValue("startdate")
|
||||||
endDate := r.FormValue("enddate")
|
endDateStr := r.FormValue("enddate")
|
||||||
|
startTimeStr := r.FormValue("starttime")
|
||||||
|
endTimeStr := r.FormValue("endtime")
|
||||||
vehicleType := r.FormValue("type")
|
vehicleType := r.FormValue("type")
|
||||||
automatic := r.FormValue("automatic") == "on"
|
automatic := r.FormValue("automatic") == "on"
|
||||||
|
|
||||||
|
// Default times if not provided
|
||||||
|
if startTimeStr == "" {
|
||||||
|
startTimeStr = "00:00"
|
||||||
|
}
|
||||||
|
if endTimeStr == "" {
|
||||||
|
endTimeStr = "23:59"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse dates and times in Europe/Paris timezone
|
||||||
|
paris, _ := time.LoadLocation("Europe/Paris")
|
||||||
|
var startDate, endDate time.Time
|
||||||
|
if startDateStr != "" {
|
||||||
|
startDate, _ = time.ParseInLocation("2006-01-02 15:04", startDateStr+" "+startTimeStr, paris)
|
||||||
|
}
|
||||||
|
if endDateStr != "" {
|
||||||
|
endDate, _ = time.ParseInLocation("2006-01-02 15:04", endDateStr+" "+endTimeStr, paris)
|
||||||
|
}
|
||||||
|
|
||||||
// Call business logic
|
// Call business logic
|
||||||
result, err := h.applicationHandler.SearchVehicles(r.Context(), beneficiaryID, startDate, endDate, vehicleType, automatic)
|
result, err := h.applicationHandler.SearchVehicles(r.Context(), beneficiaryID, startDate, endDate, vehicleType, automatic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,8 +79,23 @@ func (h *Handler) BookVehicleHTTPHandler() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract form data
|
// Extract form data
|
||||||
startDate := r.FormValue("startdate")
|
startDateStr := r.FormValue("startdate")
|
||||||
endDate := r.FormValue("enddate")
|
endDateStr := r.FormValue("enddate")
|
||||||
|
startTimeStr := r.FormValue("starttime")
|
||||||
|
endTimeStr := r.FormValue("endtime")
|
||||||
|
|
||||||
|
// Default times if not provided
|
||||||
|
if startTimeStr == "" {
|
||||||
|
startTimeStr = "00:00"
|
||||||
|
}
|
||||||
|
if endTimeStr == "" {
|
||||||
|
endTimeStr = "23:59"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse dates and times in Europe/Paris timezone
|
||||||
|
paris, _ := time.LoadLocation("Europe/Paris")
|
||||||
|
startDate, _ := time.ParseInLocation("2006-01-02 15:04", startDateStr+" "+startTimeStr, paris)
|
||||||
|
endDate, _ := time.ParseInLocation("2006-01-02 15:04", endDateStr+" "+endTimeStr, paris)
|
||||||
|
|
||||||
// Extract documents
|
// Extract documents
|
||||||
documents := make(map[string]io.Reader)
|
documents := make(map[string]io.Reader)
|
||||||
|
|||||||
@@ -178,9 +178,34 @@ func (h *Handler) VehicleManagementBookingDisplayHTTPHandler() http.HandlerFunc
|
|||||||
// Parse form data
|
// Parse form data
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
|
|
||||||
|
// Extract date and time values
|
||||||
|
startDateStr := r.FormValue("startdate")
|
||||||
|
startTimeStr := r.FormValue("starttime")
|
||||||
|
endDateStr := r.FormValue("enddate")
|
||||||
|
endTimeStr := r.FormValue("endtime")
|
||||||
|
|
||||||
|
// Parse dates with times in Europe/Paris timezone
|
||||||
|
paris, _ := time.LoadLocation("Europe/Paris")
|
||||||
|
|
||||||
|
var startDate, endDate *time.Time
|
||||||
|
if startDateStr != "" {
|
||||||
|
if startTimeStr == "" {
|
||||||
|
startTimeStr = "00:00"
|
||||||
|
}
|
||||||
|
t, _ := time.ParseInLocation("2006-01-02 15:04", startDateStr+" "+startTimeStr, paris)
|
||||||
|
startDate = &t
|
||||||
|
}
|
||||||
|
if endDateStr != "" {
|
||||||
|
if endTimeStr == "" {
|
||||||
|
endTimeStr = "23:59"
|
||||||
|
}
|
||||||
|
t, _ := time.ParseInLocation("2006-01-02 15:04", endDateStr+" "+endTimeStr, paris)
|
||||||
|
endDate = &t
|
||||||
|
}
|
||||||
|
|
||||||
err := h.applicationHandler.UpdateBooking(r.Context(), bookingID,
|
err := h.applicationHandler.UpdateBooking(r.Context(), bookingID,
|
||||||
r.FormValue("startdate"),
|
startDate,
|
||||||
r.FormValue("enddate"),
|
endDate,
|
||||||
r.FormValue("unavailablefrom"),
|
r.FormValue("unavailablefrom"),
|
||||||
r.FormValue("unavailableto"),
|
r.FormValue("unavailableto"),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user