lot of new functionalities
This commit is contained in:
247
servers/web/application/administration.go
Normal file
247
servers/web/application/administration.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) AdministrationHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Call business logic handler
|
||||
result, err := h.applicationHandler.GetAdministrationData(r.Context())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving administration data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Render HTTP response
|
||||
h.renderer.Administration(
|
||||
w, r,
|
||||
result.Accounts,
|
||||
result.Beneficiaries,
|
||||
result.Groups,
|
||||
result.Bookings,
|
||||
result.Events,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdministrationCreateGroupHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract and validate form parameters
|
||||
name := r.FormValue("name")
|
||||
if name == "" {
|
||||
log.Error().Str("name", name).Msg("Invalid name")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract modules configuration
|
||||
modules := map[string]any{
|
||||
"beneficiaries": r.FormValue("modules.beneficiaries") == "on",
|
||||
"journeys": r.FormValue("modules.journeys") == "on",
|
||||
"vehicles": r.FormValue("modules.vehicles") == "on",
|
||||
"vehicles_management": r.FormValue("modules.vehicles_management") == "on",
|
||||
"events": r.FormValue("modules.events") == "on",
|
||||
"agenda": r.FormValue("modules.agenda") == "on",
|
||||
"groups": r.FormValue("modules.groups") == "on",
|
||||
"administration": r.FormValue("modules.administration") == "on",
|
||||
"support": r.FormValue("modules.support") == "on",
|
||||
"group_module": r.FormValue("modules.group_module") == "on",
|
||||
"organized_carpool": r.FormValue("modules.organized_carpool") == "on",
|
||||
"solidarity_transport": r.FormValue("modules.solidarity_transport") == "on",
|
||||
}
|
||||
|
||||
// Call business logic handler
|
||||
groupID, err := h.applicationHandler.CreateAdministrationGroup(r.Context(), name, modules)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating administration group")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to group display
|
||||
http.Redirect(w, r, "/app/administration/groups/"+groupID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, render the create group form
|
||||
h.renderer.AdministrationCreateGroup(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdministrationGroupDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
groupID := vars["groupid"]
|
||||
|
||||
// Call business logic handler
|
||||
result, err := h.applicationHandler.GetAdministrationGroupData(r.Context(), groupID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving administration group data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Render HTTP response
|
||||
h.renderer.AdministrationGroupDisplay(w, r, result.Group, result.Members, result.Admins)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdministrationGroupInviteAdminHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
groupID := vars["groupid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract form parameters
|
||||
username := r.FormValue("username")
|
||||
if username == "" {
|
||||
log.Error().Str("username", username).Msg("Invalid username")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Call business logic handler
|
||||
err := h.applicationHandler.InviteAdministrationGroupAdmin(r.Context(), groupID, username)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error inviting group admin")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect back to group display
|
||||
http.Redirect(w, r, "/app/administration/groups/"+groupID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, redirect to group display (no separate form)
|
||||
http.Redirect(w, r, "/app/administration/groups/"+groupID, http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdministrationGroupInviteMemberHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
groupID := vars["groupid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract form parameters
|
||||
username := r.FormValue("username")
|
||||
if username == "" {
|
||||
log.Error().Str("username", username).Msg("Invalid username")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Call business logic handler
|
||||
err := h.applicationHandler.InviteAdministrationGroupMember(r.Context(), groupID, username)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error inviting group member")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect back to group display
|
||||
http.Redirect(w, r, "/app/administration/groups/"+groupID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, redirect to group display (no separate form)
|
||||
http.Redirect(w, r, "/app/administration/groups/"+groupID, http.StatusFound)
|
||||
}
|
||||
}
|
||||
func (h *Handler) AdminStatsVehiclesHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.applicationHandler.GetVehiclesStats()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicles stats")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AdminStatVehicles(w, r, result.Vehicles, result.Bookings, result.Groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdminStatsBookingsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract filter parameters from query
|
||||
status := r.URL.Query().Get("status")
|
||||
dateStart := r.URL.Query().Get("date_start")
|
||||
dateEnd := r.URL.Query().Get("date_end")
|
||||
|
||||
// Default to last month if no dates specified
|
||||
if dateStart == "" {
|
||||
dateStart = time.Now().AddDate(0, -1, 0).Format("2006-01-02")
|
||||
}
|
||||
if dateEnd == "" {
|
||||
dateEnd = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBookingsStats(status, dateStart, dateEnd)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving bookings stats")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare filters map for template
|
||||
filters := map[string]string{
|
||||
"status": status,
|
||||
"date_start": dateStart,
|
||||
"date_end": dateEnd,
|
||||
}
|
||||
|
||||
h.renderer.AdminStatBookings(w, r, result.Vehicles, result.Bookings, result.Groups, result.BeneficiariesMap, filters)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdminStatsBeneficiariesHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.applicationHandler.GetBeneficiariesStats()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving beneficiaries stats")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AdminStatBeneficaires(w, r, result.Beneficiaries, result.CacheID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AdminStatsEventsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.applicationHandler.GetEventsStats()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving events stats")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AdminStatEvents(w, r, result.Events, result.Groups)
|
||||
}
|
||||
}
|
||||
469
servers/web/application/agenda.go
Normal file
469
servers/web/application/agenda.go
Normal file
@@ -0,0 +1,469 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type EventsForm struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Address any `json:"address,omitempty"`
|
||||
Allday bool `json:"allday"`
|
||||
Startdate *time.Time `json:"startdate"`
|
||||
Enddate *time.Time `json:"enddate"`
|
||||
Starttime string `json:"starttime"`
|
||||
Endtime string `json:"endtime"`
|
||||
MaxSubscribers int `json:"max_subscribers"`
|
||||
}
|
||||
|
||||
func parseEventsForm(r *http.Request) (*EventsForm, error) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var startdate *time.Time
|
||||
var enddate *time.Time
|
||||
|
||||
if r.PostFormValue("startdate") != "" {
|
||||
d, err := time.Parse("2006-01-02", r.PostFormValue("startdate"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startdate = &d
|
||||
}
|
||||
|
||||
if r.PostFormValue("enddate") != "" {
|
||||
d, err := time.Parse("2006-01-02", r.PostFormValue("enddate"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enddate = &d
|
||||
}
|
||||
|
||||
max_subscribers, err := strconv.Atoi(r.PostFormValue("max_subscribers"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
formData := &EventsForm{
|
||||
Name: r.PostFormValue("name"),
|
||||
Type: r.PostFormValue("type"),
|
||||
Description: r.PostFormValue("description"),
|
||||
Startdate: startdate,
|
||||
Enddate: enddate,
|
||||
Starttime: r.PostFormValue("starttime"),
|
||||
Endtime: r.PostFormValue("endtime"),
|
||||
MaxSubscribers: max_subscribers,
|
||||
}
|
||||
|
||||
if r.PostFormValue("allday") == "true" {
|
||||
formData.Allday = true
|
||||
}
|
||||
|
||||
if r.PostFormValue("address") != "" {
|
||||
var a any
|
||||
json.Unmarshal([]byte(r.PostFormValue("address")), &a)
|
||||
|
||||
formData.Address = a
|
||||
}
|
||||
|
||||
validate := formvalidators.New()
|
||||
if err := validate.Struct(formData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return formData, nil
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaHomeHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
result, err := h.applicationHandler.GetAgendaEvents(r.Context(), &today, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving agenda events")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AgendaHome(w, r, result.Events, result.Groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaHistoryHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.applicationHandler.GetAgendaEvents(r.Context(), nil, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving agenda events")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AgendaHistory(w, r, result.Events, result.Groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaCreateEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
eventForm, err := parseEventsForm(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error parsing event form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle file upload if present
|
||||
var file io.Reader
|
||||
var filename string
|
||||
var fileSize int64
|
||||
var documentType, documentName string
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if strings.HasPrefix(contentType, "multipart/form-data") {
|
||||
err = r.ParseMultipartForm(100 * 1024 * 1024) // 100 MB limit
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error parsing multipart form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
uploadedFile, header, err := r.FormFile("file-upload")
|
||||
if err == nil {
|
||||
defer uploadedFile.Close()
|
||||
file = uploadedFile
|
||||
filename = header.Filename
|
||||
fileSize = header.Size
|
||||
documentType = r.FormValue("file_type")
|
||||
documentName = r.FormValue("file_name")
|
||||
} else if err != http.ErrMissingFile {
|
||||
log.Error().Err(err).Msg("error retrieving file")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
eventID, err := h.applicationHandler.CreateAgendaEvent(
|
||||
r.Context(),
|
||||
eventForm.Name,
|
||||
eventForm.Type,
|
||||
eventForm.Description,
|
||||
eventForm.Address,
|
||||
eventForm.Allday,
|
||||
eventForm.Startdate,
|
||||
eventForm.Enddate,
|
||||
eventForm.Starttime,
|
||||
eventForm.Endtime,
|
||||
eventForm.MaxSubscribers,
|
||||
file,
|
||||
filename,
|
||||
fileSize,
|
||||
documentType,
|
||||
documentName,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, render the create event form with config data
|
||||
documentTypes := h.cfg.GetStringSlice("modules.agenda.documents_types")
|
||||
fileTypes := h.cfg.GetStringMapString("storage.files.file_types")
|
||||
h.renderer.AgendaCreateEvent(w, r, documentTypes, fileTypes, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaDisplayEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
|
||||
result, err := h.applicationHandler.GetAgendaEvent(r.Context(), eventID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
documentTypes := h.cfg.GetStringSlice("modules.agenda.documents_types")
|
||||
fileTypes := h.cfg.GetStringMapString("storage.files.file_types")
|
||||
h.renderer.AgendaDisplayEvent(w, r, result.Event, result.Group, documentTypes, fileTypes, result.Documents, result.Subscribers, result.Accounts)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaUpdateEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
eventForm, err := parseEventsForm(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error parsing event form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
updatedEventID, err := h.applicationHandler.UpdateAgendaEvent(
|
||||
r.Context(),
|
||||
eventID,
|
||||
eventForm.Name,
|
||||
eventForm.Type,
|
||||
eventForm.Description,
|
||||
eventForm.Address,
|
||||
eventForm.Allday,
|
||||
eventForm.Startdate,
|
||||
eventForm.Enddate,
|
||||
eventForm.Starttime,
|
||||
eventForm.Endtime,
|
||||
eventForm.MaxSubscribers,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", updatedEventID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, render the update event form
|
||||
result, err := h.applicationHandler.GetAgendaEvent(r.Context(), eventID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AgendaUpdateEvent(w, r, result.Event)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaDeleteEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
if err := h.applicationHandler.DeleteAgendaEvent(r.Context(), eventID); err != nil {
|
||||
log.Error().Err(err).Msg("error deleting agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/agenda/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, render the delete confirmation form
|
||||
result, err := h.applicationHandler.GetAgendaEvent(r.Context(), eventID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AgendaDeleteEvent(w, r, result.Event)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaSubscribeEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
subscriber := r.FormValue("subscriber")
|
||||
|
||||
// Get current user and group information
|
||||
current_group, err := h.currentGroup(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting current group")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
current_user_token, current_user_claims, err := h.currentUser(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting current user")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
subscriptionData := map[string]any{
|
||||
"subscribed_by": map[string]any{
|
||||
"user": map[string]any{
|
||||
"id": current_user_token.Subject,
|
||||
"display_name": current_user_claims["first_name"].(string) + " " + current_user_claims["last_name"].(string),
|
||||
"email": current_user_claims["email"].(string),
|
||||
},
|
||||
"group": map[string]any{
|
||||
"id": current_group.ID,
|
||||
"name": current_group.Data["name"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.applicationHandler.SubscribeToAgendaEvent(r.Context(), eventID, subscriber, subscriptionData); err != nil {
|
||||
log.Error().Err(err).Msg("error subscribing to agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, redirect to event display
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaDeleteSubscribeEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
subscribeID := vars["subscribeid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
motif := r.FormValue("motif")
|
||||
|
||||
// Get current user and group information
|
||||
current_group, err := h.currentGroup(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting current group")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
current_user_token, current_user_claims, err := h.currentUser(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting current user")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.applicationHandler.UnsubscribeFromAgendaEvent(
|
||||
r.Context(),
|
||||
eventID,
|
||||
subscribeID,
|
||||
motif,
|
||||
current_user_token.Subject,
|
||||
current_user_claims["first_name"].(string)+" "+current_user_claims["last_name"].(string),
|
||||
current_user_claims["email"].(string),
|
||||
current_group.ID,
|
||||
current_group.Data["name"].(string),
|
||||
); err != nil {
|
||||
log.Error().Err(err).Msg("error unsubscribing from agenda event")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, render the delete subscription form
|
||||
h.renderer.AgendaDeleteSubscribeEvent(w, r, eventID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) AgendaHistoryEventHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
|
||||
result, err := h.applicationHandler.GetAgendaEventHistory(r.Context(), eventID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving agenda event history")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.AgendaHistoryEvent(w, r, result.Event, result.Group, result.Subscribers, result.Accounts)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) EventDocumentsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
|
||||
if err := r.ParseMultipartForm(100 * 1024 * 1024); err != nil { // 100 MB limit
|
||||
log.Error().Err(err).Msg("error parsing multipart form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
documentType := r.FormValue("type")
|
||||
documentName := r.FormValue("name")
|
||||
|
||||
file, header, err := r.FormFile("file-upload")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving file")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := h.applicationHandler.AddEventDocument(r.Context(), eventID, file, header.Filename, header.Size, documentType, documentName); err != nil {
|
||||
log.Error().Err(err).Msg("error adding event document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) EventDocumentDownloadHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
eventID := vars["eventid"]
|
||||
document := vars["document"]
|
||||
|
||||
file, info, err := h.applicationHandler.GetEventDocument(r.Context(), eventID, document)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving event document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", info.ContentType)
|
||||
if _, err = io.Copy(w, file); err != nil {
|
||||
log.Error().Err(err).Msg("error copying file content")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
324
servers/web/application/beneficiaries.go
Normal file
324
servers/web/application/beneficiaries.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type BeneficiariesForm struct {
|
||||
FirstName string `json:"first_name" validate:"required"`
|
||||
LastName string `json:"last_name" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Birthdate *time.Time `json:"birthdate" validate:"required"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
|
||||
FileNumber string `json:"file_number"`
|
||||
Address any `json:"address,omitempty"`
|
||||
Gender string `json:"gender"`
|
||||
OtherProperties any `json:"other_properties,omitempty"`
|
||||
}
|
||||
|
||||
func parseBeneficiariesForm(r *http.Request) (*BeneficiariesForm, error) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var date *time.Time
|
||||
|
||||
if r.PostFormValue("birthdate") != "" {
|
||||
d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
date = &d
|
||||
}
|
||||
|
||||
formData := &BeneficiariesForm{
|
||||
FirstName: r.PostFormValue("first_name"),
|
||||
LastName: r.PostFormValue("last_name"),
|
||||
Email: r.PostFormValue("email"),
|
||||
Birthdate: date,
|
||||
PhoneNumber: r.PostFormValue("phone_number"),
|
||||
FileNumber: r.PostFormValue("file_number"),
|
||||
Gender: r.PostFormValue("gender"),
|
||||
}
|
||||
|
||||
if r.PostFormValue("address") != "" {
|
||||
var a any
|
||||
json.Unmarshal([]byte(r.PostFormValue("address")), &a)
|
||||
formData.Address = a
|
||||
}
|
||||
|
||||
if r.PostFormValue("other_properties") != "" {
|
||||
var a any
|
||||
json.Unmarshal([]byte(r.PostFormValue("other_properties")), &a)
|
||||
formData.OtherProperties = a
|
||||
}
|
||||
|
||||
validate := formvalidators.New()
|
||||
if err := validate.Struct(formData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return formData, nil
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiariesListHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract search and archived filters from query parameters
|
||||
searchFilter := ""
|
||||
if search := r.URL.Query().Get("search"); search != "" {
|
||||
searchFilter = search
|
||||
}
|
||||
|
||||
archivedFilter := false
|
||||
if archived := r.URL.Query().Get("archived"); archived == "true" {
|
||||
archivedFilter = true
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBeneficiaries(r.Context(), searchFilter, archivedFilter)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving beneficiaries")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.BeneficiariesList(w, r, result.Accounts, result.CacheID, archivedFilter)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryCreateHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
beneficiaryForm, err := parseBeneficiariesForm(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error parsing beneficiary form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
beneficiaryID, err := h.applicationHandler.CreateBeneficiary(
|
||||
r.Context(),
|
||||
beneficiaryForm.FirstName,
|
||||
beneficiaryForm.LastName,
|
||||
beneficiaryForm.Email,
|
||||
beneficiaryForm.Birthdate,
|
||||
beneficiaryForm.PhoneNumber,
|
||||
beneficiaryForm.FileNumber,
|
||||
beneficiaryForm.Address,
|
||||
beneficiaryForm.Gender,
|
||||
beneficiaryForm.OtherProperties,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating beneficiary")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.BeneficiaryCreate(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
// Extract tab parameter
|
||||
tab := r.URL.Query().Get("tab")
|
||||
if tab == "" {
|
||||
tab = "documents" // Default tab
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBeneficiaryData(r.Context(), beneficiaryID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving beneficiary data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
beneficiariesFileTypes := h.cfg.GetStringSlice("modules.beneficiaries.documents_types")
|
||||
fileTypesMap := h.cfg.GetStringMapString("storage.files.file_types")
|
||||
|
||||
h.renderer.BeneficiaryDisplay(w, r, result.Account, result.Bookings, result.Organizations, beneficiariesFileTypes, fileTypesMap, result.Documents, result.EventsList, result.SolidarityTransportStats, result.SolidarityTransportBookings, result.SolidarityDriversMap, result.OrganizedCarpoolStats, result.OrganizedCarpoolBookings, result.OrganizedCarpoolDriversMap, result.WalletBalance, tab)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryUpdateHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
beneficiaryForm, err := parseBeneficiariesForm(r)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error parsing beneficiary form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
updatedBeneficiaryID, err := h.applicationHandler.UpdateBeneficiary(
|
||||
r.Context(),
|
||||
beneficiaryID,
|
||||
beneficiaryForm.FirstName,
|
||||
beneficiaryForm.LastName,
|
||||
beneficiaryForm.Email,
|
||||
beneficiaryForm.Birthdate,
|
||||
beneficiaryForm.PhoneNumber,
|
||||
beneficiaryForm.FileNumber,
|
||||
beneficiaryForm.Address,
|
||||
beneficiaryForm.Gender,
|
||||
beneficiaryForm.OtherProperties,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating beneficiary")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", updatedBeneficiaryID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// For GET requests, just get the basic beneficiary account data
|
||||
result, err := h.applicationHandler.GetBeneficiary(r.Context(), beneficiaryID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving beneficiary for update")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.BeneficiaryUpdate(w, r, result.Account)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryArchiveHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
if err := h.applicationHandler.ArchiveBeneficiary(r.Context(), beneficiaryID); err != nil {
|
||||
log.Error().Err(err).Msg("error archiving beneficiary")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryUnarchiveHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
if err := h.applicationHandler.UnarchiveBeneficiary(r.Context(), beneficiaryID); err != nil {
|
||||
log.Error().Err(err).Msg("error unarchiving beneficiary")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryPictureHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
imageData, contentType, err := h.applicationHandler.GetBeneficiaryPicture(r.Context(), beneficiaryID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error generating beneficiary picture")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(imageData)))
|
||||
if _, err := w.Write(imageData); err != nil {
|
||||
log.Error().Err(err).Msg("unable to write image")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryDocumentsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
if err := r.ParseMultipartForm(100 * 1024 * 1024); err != nil { // 100 MB limit
|
||||
log.Error().Err(err).Msg("error parsing multipart form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
documentType := r.FormValue("type")
|
||||
documentName := r.FormValue("name")
|
||||
|
||||
file, header, err := r.FormFile("file-upload")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving file")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := h.applicationHandler.AddBeneficiaryDocument(r.Context(), beneficiaryID, file, header.Filename, header.Size, documentType, documentName); err != nil {
|
||||
log.Error().Err(err).Msg("error adding beneficiary document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryDocumentDownloadHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
document := vars["document"]
|
||||
|
||||
file, info, err := h.applicationHandler.GetBeneficiaryDocument(r.Context(), beneficiaryID, document)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving beneficiary document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", info.ContentType)
|
||||
if _, err = io.Copy(w, file); err != nil {
|
||||
log.Error().Err(err).Msg("error copying file content")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BeneficiaryDocumentDeleteHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
document := vars["document"]
|
||||
|
||||
if err := h.applicationHandler.DeleteBeneficiaryDocument(r.Context(), beneficiaryID, document); err != nil {
|
||||
log.Error().Err(err).Msg("error deleting beneficiary document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
74
servers/web/application/dashboard.go
Normal file
74
servers/web/application/dashboard.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) DashboardHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse driver address geography filter
|
||||
driverAddressGeo := r.URL.Query().Get("driver_address_geo")
|
||||
driverAddressGeoLayer, driverAddressGeoCode := "", ""
|
||||
if driverAddressGeo != "" {
|
||||
parts := strings.SplitN(driverAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
driverAddressGeoLayer, driverAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetDashboardData(r.Context(), driverAddressGeoLayer, driverAddressGeoCode)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving dashboard data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Enrich geography filters with names from geography service
|
||||
var enrichedGeoFilters []map[string]string
|
||||
if h.cfg.GetBool("geography.filters.enabled") {
|
||||
geoFilters := h.cfg.Get("geography.filters.geographies")
|
||||
if geoList, ok := geoFilters.([]any); ok {
|
||||
for _, geoItem := range geoList {
|
||||
if geoMap, ok := geoItem.(map[string]any); ok {
|
||||
layer := ""
|
||||
code := ""
|
||||
if l, ok := geoMap["layer"].(string); ok {
|
||||
layer = l
|
||||
}
|
||||
if c, ok := geoMap["code"].(string); ok {
|
||||
code = c
|
||||
}
|
||||
|
||||
enrichedGeo := map[string]string{
|
||||
"layer": layer,
|
||||
"code": code,
|
||||
"name": code, // Default to code if name fetch fails
|
||||
}
|
||||
|
||||
// Fetch name from geography service
|
||||
if layer != "" && code != "" {
|
||||
if geoFeature, err := h.services.Geography.Find(layer, code); err == nil {
|
||||
if name := geoFeature.Properties.MustString("nom"); name != "" {
|
||||
enrichedGeo["name"] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enrichedGeoFilters = append(enrichedGeoFilters, enrichedGeo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
sort.Slice(enrichedGeoFilters, func(i, j int) bool {
|
||||
return enrichedGeoFilters[i]["name"] < enrichedGeoFilters[j]["name"]
|
||||
})
|
||||
}
|
||||
|
||||
h.renderer.Dashboard(w, r, result.Accounts, len(result.Accounts), len(result.Members), result.Events, result.Bookings, result.SolidarityDrivers, result.OrganizedCarpoolDrivers, driverAddressGeo, enrichedGeoFilters)
|
||||
}
|
||||
}
|
||||
11
servers/web/application/directory.go
Normal file
11
servers/web/application/directory.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h *Handler) DirectoryHomeHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderer.DirectoryHome(w, r)
|
||||
}
|
||||
}
|
||||
54
servers/web/application/group.go
Normal file
54
servers/web/application/group.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) GroupSettingsDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
group := g.(storage.Group)
|
||||
|
||||
result, err := h.applicationHandler.GetGroupSettings(r.Context(), group.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting group settings")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.GroupSettingsDisplay(w, r, result.Group, result.GroupMembers, result.Admins)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) GroupSettingsInviteMemberHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
group := g.(storage.Group)
|
||||
|
||||
r.ParseForm()
|
||||
username := r.FormValue("username")
|
||||
|
||||
err := h.applicationHandler.InviteMemberToGroup(r.Context(), group.ID, username)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error inviting member to group")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/group/settings", http.StatusFound)
|
||||
}
|
||||
}
|
||||
101
servers/web/application/group_module.go
Normal file
101
servers/web/application/group_module.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) GroupsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.applicationHandler.GetGroups(r.Context())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting groups")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.Groups(w, r, result.Groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CreateGroupModuleHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
name := r.FormValue("name")
|
||||
groupType := r.FormValue("type")
|
||||
description := r.FormValue("description")
|
||||
address := r.PostFormValue("address")
|
||||
|
||||
result, err := h.applicationHandler.CreateGroupModule(r.Context(), name, groupType, description, address)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating group module")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/group_module/groups/"+result.GroupID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetGroupModuleCreateData(r.Context())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting group module create data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.CreateGroupModule(w, r, result.GroupTypes)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DisplayGroupModuleHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
groupID := vars["groupid"]
|
||||
|
||||
if r.Method == "POST" || r.FormValue("beneficiaryid") != "" {
|
||||
r.ParseForm()
|
||||
beneficiaryID := r.FormValue("beneficiaryid")
|
||||
|
||||
err := h.applicationHandler.SubscribeBeneficiaryToGroup(r.Context(), groupID, beneficiaryID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error subscribing beneficiary to group")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/group_module/groups/"+groupID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Get current user's group from context
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
currentUserGroup := g.(storage.Group)
|
||||
|
||||
// Parse search filter
|
||||
searchFilter := ""
|
||||
if searchFilters, ok := r.URL.Query()["search"]; ok && len(searchFilters) > 0 {
|
||||
searchFilter = searchFilters[0]
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.DisplayGroupModule(r.Context(), groupID, searchFilter, currentUserGroup)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error displaying group module")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.DisplayGroupModule(w, r, result.GroupID, result.Accounts, result.CacheID,
|
||||
result.Searched, result.Beneficiary, result.Group, result.AccountsBeneficiaire)
|
||||
}
|
||||
}
|
||||
60
servers/web/application/handler.go
Normal file
60
servers/web/application/handler.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
cfg *viper.Viper
|
||||
renderer *renderer.Renderer
|
||||
applicationHandler *application.ApplicationHandler
|
||||
idp *identification.IdentificationProvider
|
||||
services *services.ServicesHandler
|
||||
}
|
||||
|
||||
func NewHandler(cfg *viper.Viper, renderer *renderer.Renderer, applicationHandler *application.ApplicationHandler, idp *identification.IdentificationProvider, services *services.ServicesHandler) *Handler {
|
||||
return &Handler{
|
||||
cfg: cfg,
|
||||
renderer: renderer,
|
||||
applicationHandler: applicationHandler,
|
||||
idp: idp,
|
||||
services: services,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) currentGroup(r *http.Request) (current_group storage.Group, err error) {
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
return storage.Group{}, errors.New("current group not found")
|
||||
}
|
||||
current_group = g.(storage.Group)
|
||||
|
||||
return current_group, nil
|
||||
}
|
||||
|
||||
func (h *Handler) currentUser(r *http.Request) (current_user_token *oidc.IDToken, current_user_claims map[string]any, err error) {
|
||||
// Get current user ID
|
||||
u := r.Context().Value(identification.IdtokenKey)
|
||||
if u == nil {
|
||||
return nil, nil, errors.New("current user not found")
|
||||
}
|
||||
current_user_token = u.(*oidc.IDToken)
|
||||
|
||||
// Get current user claims
|
||||
c := r.Context().Value(identification.ClaimsKey)
|
||||
if c == nil {
|
||||
return nil, nil, errors.New("current user claims not found")
|
||||
}
|
||||
current_user_claims = c.(map[string]any)
|
||||
|
||||
return current_user_token, current_user_claims, nil
|
||||
}
|
||||
418
servers/web/application/journeys.go
Normal file
418
servers/web/application/journeys.go
Normal file
@@ -0,0 +1,418 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
72
servers/web/application/members.go
Normal file
72
servers/web/application/members.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) MembersListHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := h.applicationHandler.GetMembers(r.Context())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving members")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.MembersList(w, r, result.Accounts, result.CacheID, result.GroupsNames)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) MemberDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
memberID := vars["adminid"]
|
||||
|
||||
result, err := h.applicationHandler.GetMemberData(r.Context(), memberID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving member data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.MemberDisplay(w, r, result.Account, result.GroupsNames)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) MemberUpdateHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
memberID := vars["adminid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
firstName := r.PostFormValue("first_name")
|
||||
lastName := r.PostFormValue("last_name")
|
||||
email := r.PostFormValue("email")
|
||||
phoneNumber := r.PostFormValue("phone_number")
|
||||
gender := r.PostFormValue("gender")
|
||||
|
||||
updatedMemberID, err := h.applicationHandler.UpdateMember(r.Context(), memberID, firstName, lastName, email, phoneNumber, gender)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating member")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/members/%s", updatedMemberID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetMember(r.Context(), memberID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving member for update")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.MemberUpdate(w, r, result.Account)
|
||||
}
|
||||
}
|
||||
596
servers/web/application/organized_carpool.go
Normal file
596
servers/web/application/organized_carpool.go
Normal file
@@ -0,0 +1,596 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/paulmach/orb/geojson"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) OrganizedCarpoolOverviewHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse form to get both query params and form data
|
||||
r.ParseForm()
|
||||
|
||||
// Extract filter parameters
|
||||
tab := r.FormValue("tab")
|
||||
if tab == "" {
|
||||
tab = "carpoolService" // Default to showing current bookings
|
||||
}
|
||||
|
||||
// Extract archived filter
|
||||
archivedFilter := false
|
||||
if archived := r.URL.Query().Get("archived"); archived == "true" {
|
||||
archivedFilter = true
|
||||
}
|
||||
|
||||
// Apply filters conditionally based on tab
|
||||
var status, driverID, startDate, endDate, departureGeo, destinationGeo, passengerAddressGeo, driverAddressGeo string
|
||||
var histStatus, histDriverID, histStartDate, histEndDate, histDepartureGeo, histDestinationGeo, histPassengerAddressGeo string
|
||||
|
||||
// Driver address geography filter (applies when on drivers tab)
|
||||
if tab == "drivers" {
|
||||
driverAddressGeo = r.FormValue("driver_address_geo")
|
||||
}
|
||||
|
||||
if tab == "carpoolService" {
|
||||
status = r.FormValue("status")
|
||||
driverID = r.FormValue("driver_id")
|
||||
startDate = r.FormValue("date_start")
|
||||
endDate = r.FormValue("date_end")
|
||||
departureGeo = r.FormValue("departure_geo")
|
||||
destinationGeo = r.FormValue("destination_geo")
|
||||
passengerAddressGeo = r.FormValue("passenger_address_geo")
|
||||
}
|
||||
|
||||
// History filters (apply when on carpoolHistory tab)
|
||||
if tab == "carpoolHistory" {
|
||||
histStatus = r.FormValue("status")
|
||||
histDriverID = r.FormValue("driver_id")
|
||||
histStartDate = r.FormValue("date_start")
|
||||
histEndDate = r.FormValue("date_end")
|
||||
histDepartureGeo = r.FormValue("departure_geo")
|
||||
histDestinationGeo = r.FormValue("destination_geo")
|
||||
histPassengerAddressGeo = r.FormValue("passenger_address_geo")
|
||||
}
|
||||
|
||||
// Set default history dates if not provided
|
||||
if histStartDate == "" {
|
||||
histStartDate = time.Now().Add(-30 * 24 * time.Hour).Format("2006-01-02")
|
||||
}
|
||||
if histEndDate == "" {
|
||||
histEndDate = time.Now().Add(-24 * time.Hour).Format("2006-01-02")
|
||||
}
|
||||
|
||||
// Parse geography parameters (format: "layer:code")
|
||||
departureGeoLayer, departureGeoCode := "", ""
|
||||
if departureGeo != "" {
|
||||
parts := strings.SplitN(departureGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
departureGeoLayer, departureGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
destinationGeoLayer, destinationGeoCode := "", ""
|
||||
if destinationGeo != "" {
|
||||
parts := strings.SplitN(destinationGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
destinationGeoLayer, destinationGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
passengerAddressGeoLayer, passengerAddressGeoCode := "", ""
|
||||
if passengerAddressGeo != "" {
|
||||
parts := strings.SplitN(passengerAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
passengerAddressGeoLayer, passengerAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
histDepartureGeoLayer, histDepartureGeoCode := "", ""
|
||||
if histDepartureGeo != "" {
|
||||
parts := strings.SplitN(histDepartureGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
histDepartureGeoLayer, histDepartureGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
histDestinationGeoLayer, histDestinationGeoCode := "", ""
|
||||
if histDestinationGeo != "" {
|
||||
parts := strings.SplitN(histDestinationGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
histDestinationGeoLayer, histDestinationGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
histPassengerAddressGeoLayer, histPassengerAddressGeoCode := "", ""
|
||||
if histPassengerAddressGeo != "" {
|
||||
parts := strings.SplitN(histPassengerAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
histPassengerAddressGeoLayer, histPassengerAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse driver address geography parameter
|
||||
driverAddressGeoLayer, driverAddressGeoCode := "", ""
|
||||
if driverAddressGeo != "" {
|
||||
parts := strings.SplitN(driverAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
driverAddressGeoLayer, driverAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetOrganizedCarpoolOverview(r.Context(), status, driverID, startDate, endDate, departureGeoLayer, departureGeoCode, destinationGeoLayer, destinationGeoCode, passengerAddressGeoLayer, passengerAddressGeoCode, histStatus, histDriverID, histStartDate, histEndDate, histDepartureGeoLayer, histDepartureGeoCode, histDestinationGeoLayer, histDestinationGeoCode, histPassengerAddressGeoLayer, histPassengerAddressGeoCode, archivedFilter, driverAddressGeoLayer, driverAddressGeoCode)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpool overview")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Build filters map for template
|
||||
filters := map[string]any{
|
||||
"tab": tab,
|
||||
"date_start": startDate,
|
||||
"date_end": endDate,
|
||||
"status": status,
|
||||
"driver_id": driverID,
|
||||
"departure_geo": departureGeo,
|
||||
"destination_geo": destinationGeo,
|
||||
"passenger_address_geo": passengerAddressGeo,
|
||||
"driver_address_geo": driverAddressGeo,
|
||||
}
|
||||
|
||||
histFilters := map[string]any{
|
||||
"tab": tab,
|
||||
"date_start": histStartDate,
|
||||
"date_end": histEndDate,
|
||||
"status": histStatus,
|
||||
"driver_id": histDriverID,
|
||||
"departure_geo": histDepartureGeo,
|
||||
"destination_geo": histDestinationGeo,
|
||||
"passenger_address_geo": histPassengerAddressGeo,
|
||||
}
|
||||
|
||||
// Enrich geography filters with names from geography service
|
||||
var enrichedGeoFilters []map[string]string
|
||||
if h.cfg.GetBool("geography.filters.enabled") {
|
||||
geoFilters := h.cfg.Get("geography.filters.geographies")
|
||||
if geoList, ok := geoFilters.([]any); ok {
|
||||
for _, geoItem := range geoList {
|
||||
if geoMap, ok := geoItem.(map[string]any); ok {
|
||||
layer := ""
|
||||
code := ""
|
||||
if l, ok := geoMap["layer"].(string); ok {
|
||||
layer = l
|
||||
}
|
||||
if c, ok := geoMap["code"].(string); ok {
|
||||
code = c
|
||||
}
|
||||
|
||||
enrichedGeo := map[string]string{
|
||||
"layer": layer,
|
||||
"code": code,
|
||||
"name": code, // Default to code if name fetch fails
|
||||
}
|
||||
|
||||
// Fetch name from geography service
|
||||
if layer != "" && code != "" {
|
||||
if geoFeature, err := h.services.Geography.Find(layer, code); err == nil {
|
||||
if name := geoFeature.Properties.MustString("nom"); name != "" {
|
||||
enrichedGeo["name"] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enrichedGeoFilters = append(enrichedGeoFilters, enrichedGeo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
sort.Slice(enrichedGeoFilters, func(i, j int) bool {
|
||||
return enrichedGeoFilters[i]["name"] < enrichedGeoFilters[j]["name"]
|
||||
})
|
||||
}
|
||||
|
||||
h.renderer.OrganizedCarpoolOverview(w, r, result.Accounts, result.AccountsMap, result.BeneficiariesMap, result.Bookings, result.BookingsHistory, filters, histFilters, tab, enrichedGeoFilters, archivedFilter)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolBookingDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
result, err := h.applicationHandler.GetOrganizedCarpoolBookingData(r.Context(), bookingID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpool booking")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.OrganizedCarpoolBookingDisplay(w, r, result.Booking, result.Driver, result.Passenger, result.DriverDepartureAddress, result.DriverArrivalAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolBookingStatusHTTPHandler(action string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
err := h.applicationHandler.UpdateOrganizedCarpoolBookingStatus(r.Context(), bookingID, action)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating organized carpool booking status")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/bookings/%s", bookingID), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolCreateDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
firstName := r.PostFormValue("first_name")
|
||||
lastName := r.PostFormValue("last_name")
|
||||
email := r.PostFormValue("email")
|
||||
phoneNumber := r.PostFormValue("phone_number")
|
||||
fileNumber := r.PostFormValue("file_number")
|
||||
gender := r.PostFormValue("gender")
|
||||
|
||||
var birthdate *time.Time
|
||||
if r.PostFormValue("birthdate") != "" {
|
||||
if d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate")); err == nil {
|
||||
birthdate = &d
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON address fields
|
||||
var address, addressDestination any
|
||||
if r.PostFormValue("address") != "" {
|
||||
json.Unmarshal([]byte(r.PostFormValue("address")), &address)
|
||||
}
|
||||
if r.PostFormValue("address_destination") != "" {
|
||||
json.Unmarshal([]byte(r.PostFormValue("address_destination")), &addressDestination)
|
||||
}
|
||||
|
||||
driverID, err := h.applicationHandler.CreateOrganizedCarpoolDriver(
|
||||
r.Context(),
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
birthdate,
|
||||
phoneNumber,
|
||||
fileNumber,
|
||||
address,
|
||||
addressDestination,
|
||||
gender,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating organized carpool driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.OrganizedCarpoolCreateDriver(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolDriverDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
// Extract tab parameter
|
||||
tab := r.URL.Query().Get("tab")
|
||||
if tab == "" {
|
||||
tab = "documents" // Default tab
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetOrganizedCarpoolDriverData(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpool driver data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.OrganizedCarpoolDriverDisplay(w, r, result.Driver, result.Trips, result.Documents, result.Bookings, result.BeneficiariesMap, result.Stats, result.WalletBalance, tab)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolUpdateDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
firstName := r.PostFormValue("first_name")
|
||||
lastName := r.PostFormValue("last_name")
|
||||
email := r.PostFormValue("email")
|
||||
phoneNumber := r.PostFormValue("phone_number")
|
||||
fileNumber := r.PostFormValue("file_number")
|
||||
gender := r.PostFormValue("gender")
|
||||
|
||||
var birthdate *time.Time
|
||||
if r.PostFormValue("birthdate") != "" {
|
||||
if d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate")); err == nil {
|
||||
birthdate = &d
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON address fields
|
||||
var address, addressDestination any
|
||||
if r.PostFormValue("address") != "" {
|
||||
json.Unmarshal([]byte(r.PostFormValue("address")), &address)
|
||||
}
|
||||
if r.PostFormValue("address_destination") != "" {
|
||||
json.Unmarshal([]byte(r.PostFormValue("address_destination")), &addressDestination)
|
||||
}
|
||||
|
||||
updatedDriverID, err := h.applicationHandler.UpdateOrganizedCarpoolDriver(
|
||||
r.Context(),
|
||||
driverID,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
birthdate,
|
||||
phoneNumber,
|
||||
fileNumber,
|
||||
address,
|
||||
addressDestination,
|
||||
gender,
|
||||
r.PostFormValue("other_properties"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating organized carpool driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", updatedDriverID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetOrganizedCarpoolDriver(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpool driver for update")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.OrganizedCarpoolUpdateDriver(w, r, result.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolArchiveDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
err := h.applicationHandler.ArchiveOrganizedCarpoolDriver(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error archiving organized carpool driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolUnarchiveDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
err := h.applicationHandler.UnarchiveOrganizedCarpoolDriver(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error unarchiving organized carpool driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolDriverDocumentsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
if err := r.ParseMultipartForm(100 * 1024 * 1024); err != nil { // 100 MB limit
|
||||
log.Error().Err(err).Msg("error parsing multipart form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
documentType := r.FormValue("type")
|
||||
documentName := r.FormValue("name")
|
||||
|
||||
file, header, err := r.FormFile("file-upload")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving file")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := h.applicationHandler.AddOrganizedCarpoolDriverDocument(r.Context(), driverID, file, header.Filename, header.Size, documentType, documentName); err != nil {
|
||||
log.Error().Err(err).Msg("error adding organized carpool driver document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolDocumentDownloadHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
document := vars["document"]
|
||||
|
||||
file, info, err := h.applicationHandler.GetOrganizedCarpoolDriverDocument(r.Context(), driverID, document)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpool driver document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", info.ContentType)
|
||||
if _, err = io.Copy(w, file); err != nil {
|
||||
log.Error().Err(err).Msg("error copying file content")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolDocumentDeleteHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
document := vars["document"]
|
||||
|
||||
if err := h.applicationHandler.DeleteOrganizedCarpoolDriverDocument(r.Context(), driverID, document); err != nil {
|
||||
log.Error().Err(err).Msg("error deleting organized carpool driver document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolAddTripHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
log.Error().Msg("Wrong method")
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing availabilities form")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
// Parse form data
|
||||
outwardtime := r.PostFormValue("outwardtime")
|
||||
returntime := r.PostFormValue("returntime")
|
||||
|
||||
// Parse GeoJSON features
|
||||
departure, err := geojson.UnmarshalFeature([]byte(r.PostFormValue("address_departure")))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing departure geojson")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
destination, err := geojson.UnmarshalFeature([]byte(r.PostFormValue("address_destination")))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing destination geojson")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse days
|
||||
days := map[string]bool{
|
||||
"monday": r.PostFormValue("days.monday") == "on",
|
||||
"tuesday": r.PostFormValue("days.tuesday") == "on",
|
||||
"wednesday": r.PostFormValue("days.wednesday") == "on",
|
||||
"thursday": r.PostFormValue("days.thursday") == "on",
|
||||
"friday": r.PostFormValue("days.friday") == "on",
|
||||
"saturday": r.PostFormValue("days.saturday") == "on",
|
||||
"sunday": r.PostFormValue("days.sunday") == "on",
|
||||
}
|
||||
|
||||
err = h.applicationHandler.AddOrganizedCarpoolTrip(r.Context(), driverID, outwardtime, returntime, departure, destination, days)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error adding organized carpool trip")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolDeleteTripHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
tripID := vars["tripid"]
|
||||
|
||||
err := h.applicationHandler.DeleteOrganizedCarpoolTrip(r.Context(), tripID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error deleting organized carpool trip")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) OrganizedCarpoolJourneyHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
journeyID := vars["journeyid"]
|
||||
passengerID := r.URL.Query().Get("passengerid")
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
motivation := r.PostFormValue("motivation")
|
||||
message := r.PostFormValue("message")
|
||||
doNotSend := r.PostFormValue("do_not_send") == "on"
|
||||
|
||||
bookingID, err := h.applicationHandler.CreateOrganizedCarpoolJourneyBooking(r.Context(), driverID, journeyID, passengerID, motivation, message, doNotSend)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating organized carpool journey booking")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("booking_id", bookingID).Msg("Carpool booking created successfully")
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/"), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Get current user's group
|
||||
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)
|
||||
|
||||
result, err := h.applicationHandler.GetOrganizedCarpoolJourneyData(r.Context(), driverID, journeyID, passengerID, group)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving organized carpool journey data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.OrganizedCarpoolJourney(w, r, result.Journey, result.Driver, result.Passenger, result.Beneficiaries, result.PassengerWalletBalance, result.PricingResult)
|
||||
}
|
||||
}
|
||||
36
servers/web/application/sms.go
Normal file
36
servers/web/application/sms.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) SendSMSHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
referer := r.Referer()
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("Bad request")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
message := r.PostFormValue("message")
|
||||
beneficiaryID := r.PostFormValue("beneficiaryid")
|
||||
|
||||
err := h.applicationHandler.SendSMS(r.Context(), beneficiaryID, message)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error sending SMS")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, referer, http.StatusFound)
|
||||
}
|
||||
}
|
||||
636
servers/web/application/solidarity_transport.go
Normal file
636
servers/web/application/solidarity_transport.go
Normal file
@@ -0,0 +1,636 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse form to get both query params and form data
|
||||
r.ParseForm()
|
||||
|
||||
// Extract filter parameters using the same field names as the old handler
|
||||
tab := r.FormValue("tab")
|
||||
if tab == "" {
|
||||
tab = "solidarityService" // Default to showing current bookings
|
||||
}
|
||||
|
||||
// Extract archived filter
|
||||
archivedFilter := false
|
||||
if archived := r.URL.Query().Get("archived"); archived == "true" {
|
||||
archivedFilter = true
|
||||
}
|
||||
|
||||
// Apply filters conditionally based on tab (matching old handler logic)
|
||||
var status, driverID, startDate, endDate, departureGeo, destinationGeo, passengerAddressGeo, driverAddressGeo string
|
||||
var histStatus, histDriverID, histStartDate, histEndDate, histDepartureGeo, histDestinationGeo, histPassengerAddressGeo string
|
||||
|
||||
// Driver address geography filter (applies when on drivers tab)
|
||||
if tab == "drivers" {
|
||||
driverAddressGeo = r.FormValue("driver_address_geo")
|
||||
}
|
||||
|
||||
if tab == "solidarityService" {
|
||||
status = r.FormValue("status")
|
||||
driverID = r.FormValue("driver_id")
|
||||
startDate = r.FormValue("date_start")
|
||||
endDate = r.FormValue("date_end")
|
||||
departureGeo = r.FormValue("departure_geo")
|
||||
destinationGeo = r.FormValue("destination_geo")
|
||||
passengerAddressGeo = r.FormValue("passenger_address_geo")
|
||||
}
|
||||
|
||||
// History filters (apply when on solidarityHistory tab)
|
||||
if tab == "solidarityHistory" {
|
||||
histStatus = r.FormValue("status") // Note: history uses same field names as current
|
||||
histDriverID = r.FormValue("driver_id")
|
||||
histStartDate = r.FormValue("date_start")
|
||||
histEndDate = r.FormValue("date_end")
|
||||
histDepartureGeo = r.FormValue("departure_geo")
|
||||
histDestinationGeo = r.FormValue("destination_geo")
|
||||
histPassengerAddressGeo = r.FormValue("passenger_address_geo")
|
||||
}
|
||||
|
||||
// Set default history dates if not provided
|
||||
if histStartDate == "" {
|
||||
histStartDate = time.Now().Add(-30 * 24 * time.Hour).Format("2006-01-02")
|
||||
}
|
||||
if histEndDate == "" {
|
||||
histEndDate = time.Now().Add(-24 * time.Hour).Format("2006-01-02")
|
||||
}
|
||||
|
||||
// Parse geography parameters (format: "layer:code")
|
||||
departureGeoLayer, departureGeoCode := "", ""
|
||||
if departureGeo != "" {
|
||||
parts := strings.SplitN(departureGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
departureGeoLayer, departureGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
destinationGeoLayer, destinationGeoCode := "", ""
|
||||
if destinationGeo != "" {
|
||||
parts := strings.SplitN(destinationGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
destinationGeoLayer, destinationGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
passengerAddressGeoLayer, passengerAddressGeoCode := "", ""
|
||||
if passengerAddressGeo != "" {
|
||||
parts := strings.SplitN(passengerAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
passengerAddressGeoLayer, passengerAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
histDepartureGeoLayer, histDepartureGeoCode := "", ""
|
||||
if histDepartureGeo != "" {
|
||||
parts := strings.SplitN(histDepartureGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
histDepartureGeoLayer, histDepartureGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
histDestinationGeoLayer, histDestinationGeoCode := "", ""
|
||||
if histDestinationGeo != "" {
|
||||
parts := strings.SplitN(histDestinationGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
histDestinationGeoLayer, histDestinationGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
histPassengerAddressGeoLayer, histPassengerAddressGeoCode := "", ""
|
||||
if histPassengerAddressGeo != "" {
|
||||
parts := strings.SplitN(histPassengerAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
histPassengerAddressGeoLayer, histPassengerAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse driver address geography parameter
|
||||
driverAddressGeoLayer, driverAddressGeoCode := "", ""
|
||||
if driverAddressGeo != "" {
|
||||
parts := strings.SplitN(driverAddressGeo, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
driverAddressGeoLayer, driverAddressGeoCode = parts[0], parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetSolidarityTransportOverview(r.Context(), status, driverID, startDate, endDate, departureGeoLayer, departureGeoCode, destinationGeoLayer, destinationGeoCode, passengerAddressGeoLayer, passengerAddressGeoCode, histStatus, histDriverID, histStartDate, histEndDate, histDepartureGeoLayer, histDepartureGeoCode, histDestinationGeoLayer, histDestinationGeoCode, histPassengerAddressGeoLayer, histPassengerAddressGeoCode, archivedFilter, driverAddressGeoLayer, driverAddressGeoCode)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving solidarity transport overview")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Build filters map for template (matching old handler format)
|
||||
filters := map[string]any{
|
||||
"tab": tab,
|
||||
"date_start": startDate,
|
||||
"date_end": endDate,
|
||||
"status": status,
|
||||
"driver_id": driverID,
|
||||
"departure_geo": departureGeo,
|
||||
"destination_geo": destinationGeo,
|
||||
"passenger_address_geo": passengerAddressGeo,
|
||||
"driver_address_geo": driverAddressGeo,
|
||||
}
|
||||
|
||||
histFilters := map[string]any{
|
||||
"tab": tab,
|
||||
"date_start": histStartDate,
|
||||
"date_end": histEndDate,
|
||||
"status": histStatus,
|
||||
"driver_id": histDriverID,
|
||||
"departure_geo": histDepartureGeo,
|
||||
"destination_geo": histDestinationGeo,
|
||||
"passenger_address_geo": histPassengerAddressGeo,
|
||||
}
|
||||
|
||||
// Enrich geography filters with names from geography service
|
||||
var enrichedGeoFilters []map[string]string
|
||||
if h.cfg.GetBool("geography.filters.enabled") {
|
||||
geoFilters := h.cfg.Get("geography.filters.geographies")
|
||||
if geoList, ok := geoFilters.([]any); ok {
|
||||
for _, geoItem := range geoList {
|
||||
if geoMap, ok := geoItem.(map[string]any); ok {
|
||||
layer := ""
|
||||
code := ""
|
||||
if l, ok := geoMap["layer"].(string); ok {
|
||||
layer = l
|
||||
}
|
||||
if c, ok := geoMap["code"].(string); ok {
|
||||
code = c
|
||||
}
|
||||
|
||||
enrichedGeo := map[string]string{
|
||||
"layer": layer,
|
||||
"code": code,
|
||||
"name": code, // Default to code if name fetch fails
|
||||
}
|
||||
|
||||
// Fetch name from geography service
|
||||
if layer != "" && code != "" {
|
||||
if geoFeature, err := h.services.Geography.Find(layer, code); err == nil {
|
||||
if name := geoFeature.Properties.MustString("nom"); name != "" {
|
||||
enrichedGeo["name"] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enrichedGeoFilters = append(enrichedGeoFilters, enrichedGeo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
sort.Slice(enrichedGeoFilters, func(i, j int) bool {
|
||||
return enrichedGeoFilters[i]["name"] < enrichedGeoFilters[j]["name"]
|
||||
})
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportOverview(w, r, result.Accounts, result.AccountsMap, result.BeneficiariesMap, result.Bookings, result.BookingsHistory, filters, histFilters, tab, enrichedGeoFilters, archivedFilter)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportCreateDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
firstName := r.PostFormValue("first_name")
|
||||
lastName := r.PostFormValue("last_name")
|
||||
email := r.PostFormValue("email")
|
||||
phoneNumber := r.PostFormValue("phone_number")
|
||||
gender := r.PostFormValue("gender")
|
||||
|
||||
var birthdate *time.Time
|
||||
if r.PostFormValue("birthdate") != "" {
|
||||
if d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate")); err == nil {
|
||||
birthdate = &d
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON address field
|
||||
var address any
|
||||
if r.PostFormValue("address") != "" {
|
||||
if err := json.Unmarshal([]byte(r.PostFormValue("address")), &address); err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing address JSON")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON other_properties field
|
||||
var otherProperties any
|
||||
if r.PostFormValue("other_properties") != "" {
|
||||
if err := json.Unmarshal([]byte(r.PostFormValue("other_properties")), &otherProperties); err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing other_properties JSON")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
driverID, err := h.applicationHandler.CreateSolidarityTransportDriver(
|
||||
r.Context(),
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
birthdate,
|
||||
phoneNumber,
|
||||
address,
|
||||
gender,
|
||||
otherProperties,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating solidarity transport driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportCreateDriver(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportUpdateDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
firstName := r.PostFormValue("first_name")
|
||||
lastName := r.PostFormValue("last_name")
|
||||
email := r.PostFormValue("email")
|
||||
phoneNumber := r.PostFormValue("phone_number")
|
||||
gender := r.PostFormValue("gender")
|
||||
|
||||
var birthdate *time.Time
|
||||
if r.PostFormValue("birthdate") != "" {
|
||||
if d, err := time.Parse("2006-01-02", r.PostFormValue("birthdate")); err == nil {
|
||||
birthdate = &d
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON address field
|
||||
var address any
|
||||
if r.PostFormValue("address") != "" {
|
||||
if err := json.Unmarshal([]byte(r.PostFormValue("address")), &address); err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing address JSON")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Parse JSON other_properties field
|
||||
var otherProperties any
|
||||
if r.PostFormValue("other_properties") != "" {
|
||||
if err := json.Unmarshal([]byte(r.PostFormValue("other_properties")), &otherProperties); err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing other_properties JSON")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
updatedDriverID, err := h.applicationHandler.UpdateSolidarityTransportDriver(
|
||||
r.Context(),
|
||||
driverID,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
birthdate,
|
||||
phoneNumber,
|
||||
address,
|
||||
gender,
|
||||
otherProperties,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating solidarity transport driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", updatedDriverID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetSolidarityTransportDriver(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving solidarity transport driver for update")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportUpdateDriver(w, r, result.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDriverDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
// Extract tab parameter
|
||||
tab := r.URL.Query().Get("tab")
|
||||
if tab == "" {
|
||||
tab = "documents" // Default tab
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetSolidarityTransportDriverData(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving solidarity transport driver data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportDriverDisplay(w, r, result.Driver, result.Availabilities, result.Documents, result.Bookings, result.BeneficiariesMap, result.Stats, result.WalletBalance, tab)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportAddAvailabilityHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
log.Error().Msg("Wrong method")
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing availabilities form")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
// Parse form data
|
||||
starttime := r.PostFormValue("starttime")
|
||||
endtime := r.PostFormValue("endtime")
|
||||
|
||||
// Parse JSON address field
|
||||
var address any
|
||||
if r.PostFormValue("address") != "" {
|
||||
if err := json.Unmarshal([]byte(r.PostFormValue("address")), &address); err != nil {
|
||||
log.Error().Err(err).Msg("failed parsing address JSON")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Parse days
|
||||
days := map[string]bool{
|
||||
"monday": r.PostFormValue("days.monday") == "on",
|
||||
"tuesday": r.PostFormValue("days.tuesday") == "on",
|
||||
"wednesday": r.PostFormValue("days.wednesday") == "on",
|
||||
"thursday": r.PostFormValue("days.thursday") == "on",
|
||||
"friday": r.PostFormValue("days.friday") == "on",
|
||||
"saturday": r.PostFormValue("days.saturday") == "on",
|
||||
"sunday": r.PostFormValue("days.sunday") == "on",
|
||||
}
|
||||
|
||||
err := h.applicationHandler.AddSolidarityTransportAvailability(r.Context(), driverID, starttime, endtime, address, days)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error adding solidarity transport availability")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportArchiveDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
err := h.applicationHandler.ArchiveSolidarityTransportDriver(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error archiving solidarity transport driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportUnarchiveDriverHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
err := h.applicationHandler.UnarchiveSolidarityTransportDriver(r.Context(), driverID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error unarchiving solidarity transport driver")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDriverDocumentsHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
|
||||
if err := r.ParseMultipartForm(100 * 1024 * 1024); err != nil { // 100 MB limit
|
||||
log.Error().Err(err).Msg("error parsing multipart form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
documentType := r.FormValue("type")
|
||||
documentName := r.FormValue("name")
|
||||
|
||||
file, header, err := r.FormFile("file-upload")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving file")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := h.applicationHandler.AddSolidarityTransportDriverDocument(r.Context(), driverID, file, header.Filename, header.Size, documentType, documentName); err != nil {
|
||||
log.Error().Err(err).Msg("error adding solidarity transport driver document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDocumentDownloadHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
document := vars["document"]
|
||||
|
||||
file, info, err := h.applicationHandler.GetSolidarityTransportDriverDocument(r.Context(), driverID, document)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving solidarity transport driver document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", info.ContentType)
|
||||
if _, err = io.Copy(w, file); err != nil {
|
||||
log.Error().Err(err).Msg("error copying file content")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDeleteAvailabilityHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
availabilityID := vars["availabilityid"]
|
||||
|
||||
err := h.applicationHandler.DeleteSolidarityTransportAvailability(r.Context(), driverID, availabilityID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error deleting solidarity transport availability")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDriverJourneyHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
journeyID := vars["journeyid"]
|
||||
passengerID := r.URL.Query().Get("passengerid")
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
motivation := r.PostFormValue("motivation")
|
||||
message := r.PostFormValue("message")
|
||||
doNotSend := r.PostFormValue("do_not_send") == "on"
|
||||
returnWaitingTimeMinutes := 0
|
||||
if r.PostFormValue("return_waiting_time") != "" {
|
||||
fmt.Sscanf(r.PostFormValue("return_waiting_time"), "%d", &returnWaitingTimeMinutes)
|
||||
}
|
||||
|
||||
bookingID, err := h.applicationHandler.CreateSolidarityTransportJourneyBooking(r.Context(), driverID, journeyID, passengerID, motivation, message, doNotSend, returnWaitingTimeMinutes)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating solidarity transport journey booking")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("booking_id", bookingID).Msg("Solidarity transport booking created successfully")
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/"), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Get current user's group
|
||||
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)
|
||||
|
||||
result, err := h.applicationHandler.GetSolidarityTransportJourneyData(r.Context(), driverID, journeyID, passengerID, group)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving solidarity transport journey data")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportDriverJourney(w, r, result.Journey, result.Driver, result.Passenger, result.Beneficiaries, result.PassengerWalletBalance, result.PricingResult)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDriverJourneyToggleNoreturnHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
journeyID := vars["journeyid"]
|
||||
|
||||
err := h.applicationHandler.ToggleSolidarityTransportJourneyNoreturn(r.Context(), driverID, journeyID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error toggling solidarity transport journey noreturn")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s/journeys/%s", driverID, journeyID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
result, err := h.applicationHandler.GetSolidarityTransportBookingData(r.Context(), bookingID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving solidarity transport booking")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.SolidarityTransportBookingDisplay(w, r, result.Booking, result.Driver, result.Passenger, result.PassengerWalletBalance)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportDocumentDeleteHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
driverID := vars["driverid"]
|
||||
document := vars["document"]
|
||||
|
||||
if err := h.applicationHandler.DeleteSolidarityTransportDriverDocument(r.Context(), driverID, document); err != nil {
|
||||
log.Error().Err(err).Msg("error deleting solidarity transport driver document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SolidarityTransportBookingStatusHTTPHandler(action string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
// Extract reason from form data for cancellations
|
||||
reason := ""
|
||||
if action == "cancel" && r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
reason = r.PostFormValue("reason")
|
||||
}
|
||||
|
||||
err := h.applicationHandler.UpdateSolidarityTransportBookingStatus(r.Context(), bookingID, action, reason, "", false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating solidarity transport booking status")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/bookings/%s", bookingID), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
40
servers/web/application/support.go
Normal file
40
servers/web/application/support.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) SupportSendHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
c := r.Context().Value(identification.ClaimsKey)
|
||||
if c == nil {
|
||||
log.Error().Msg("no current user claims")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
currentUserClaims := c.(map[string]any)
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
comment := r.PostFormValue("comment")
|
||||
userEmail := currentUserClaims["email"].(string)
|
||||
|
||||
err := h.applicationHandler.SendSupportMessage(r.Context(), comment, userEmail)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error sending support message")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// GET request - show form
|
||||
comment := r.FormValue("comment")
|
||||
h.renderer.SupportSend(w, r, comment, currentUserClaims)
|
||||
}
|
||||
}
|
||||
161
servers/web/application/vehicles.go
Normal file
161
servers/web/application/vehicles.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) VehiclesSearchHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
// Extract parameters
|
||||
beneficiaryID := r.FormValue("beneficiaryid")
|
||||
startDate := r.FormValue("startdate")
|
||||
endDate := r.FormValue("enddate")
|
||||
vehicleType := r.FormValue("type")
|
||||
automatic := r.FormValue("automatic") == "on"
|
||||
|
||||
// Call business logic
|
||||
result, err := h.applicationHandler.SearchVehicles(r.Context(), beneficiaryID, startDate, endDate, vehicleType, automatic)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error searching vehicles")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Render response
|
||||
h.renderer.VehiclesSearch(w, r, result.Beneficiaries, result.Searched, result.Vehicles, result.Beneficiary,
|
||||
result.StartDate, result.EndDate, result.MandatoryDocuments, result.FileTypesMap,
|
||||
result.BeneficiaryDocuments, result.VehicleType, result.Automatic, result.VehicleTypes, result.Groups)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BookVehicleHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract URL parameters
|
||||
vars := mux.Vars(r)
|
||||
vehicleID := vars["vehicleid"]
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
// Extract user context
|
||||
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
||||
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
||||
currentGroup := r.Context().Value(identification.GroupKey)
|
||||
|
||||
// Parse multipart form
|
||||
if err := r.ParseMultipartForm(100 * 1024 * 1024); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing multipart form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract form data
|
||||
startDate := r.FormValue("startdate")
|
||||
endDate := r.FormValue("enddate")
|
||||
|
||||
// Extract documents
|
||||
documents := make(map[string]io.Reader)
|
||||
documentHeaders := make(map[string]string)
|
||||
existingDocs := make(map[string]string)
|
||||
|
||||
// Get mandatory document types from config
|
||||
mandatoryDocTypes := h.cfg.GetStringSlice("modules.fleets.booking_documents.mandatory")
|
||||
|
||||
for _, docType := range mandatoryDocTypes {
|
||||
existingFile := r.FormValue("type-" + docType)
|
||||
if existingFile != "" {
|
||||
existingDocs[docType] = existingFile
|
||||
} else {
|
||||
file, header, err := r.FormFile("doc-" + docType)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("missing required document: " + docType)
|
||||
http.Error(w, "Document manquant : "+docType, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
documents[docType] = file
|
||||
documentHeaders[docType] = header.Filename
|
||||
}
|
||||
}
|
||||
|
||||
// Call business logic
|
||||
result, err := h.applicationHandler.BookVehicle(r.Context(), vehicleID, beneficiaryID, startDate, endDate,
|
||||
documents, documentHeaders, existingDocs, currentUserToken.Subject, currentUserClaims, currentGroup)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error booking vehicle")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to booking details
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles/bookings/%s", result.BookingID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehicleBookingDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
result, err := h.applicationHandler.GetVehicleBookingDetails(r.Context(), bookingID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting booking details")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehicleBookingDisplay(w, r, result.Booking, result.Vehicle, result.Beneficiary,
|
||||
result.Group, result.Documents, result.FileTypesMap)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesBookingsListHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get group from context
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
group := g.(storage.Group)
|
||||
|
||||
result, err := h.applicationHandler.GetVehicleBookingsList(r.Context(), group.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting bookings list")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehicleBookingsList(w, r, result.Bookings, result.VehiclesMap, result.GroupsMap)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) BookingDocumentDownloadHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
document := vars["document"]
|
||||
|
||||
fileReader, contentType, err := h.applicationHandler.GetBookingDocument(r.Context(), bookingID, document)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting booking document")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
if _, err = io.Copy(w, fileReader); err != nil {
|
||||
log.Error().Err(err).Msg("error writing document to response")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
408
servers/web/application/vehicles_management.go
Normal file
408
servers/web/application/vehicles_management.go
Normal file
@@ -0,0 +1,408 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) VehiclesManagementOverviewHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract filter parameters from request
|
||||
groupID := ""
|
||||
if g := r.Context().Value(identification.GroupKey); g != nil {
|
||||
group := g.(storage.Group)
|
||||
groupID = group.ID
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementOverview(r.Context(), groupID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicles management overview")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehiclesManagementOverview(w, r, result.Vehicles, result.VehiclesMap, result.DriversMap, result.Bookings)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesManagementBookingsListHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract filter parameters from request
|
||||
groupID := ""
|
||||
if g := r.Context().Value(identification.GroupKey); g != nil {
|
||||
group := g.(storage.Group)
|
||||
groupID = group.ID
|
||||
}
|
||||
|
||||
// Extract filter parameters from query
|
||||
status := r.URL.Query().Get("status")
|
||||
dateStart := r.URL.Query().Get("date_start")
|
||||
dateEnd := r.URL.Query().Get("date_end")
|
||||
|
||||
// Default to last month if no dates specified
|
||||
if dateStart == "" {
|
||||
dateStart = time.Now().AddDate(0, -1, 0).Format("2006-01-02")
|
||||
}
|
||||
if dateEnd == "" {
|
||||
dateEnd = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetVehiclesManagementBookingsList(r.Context(), groupID, status, dateStart, dateEnd)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicles management bookings list")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare filters map for template
|
||||
filters := map[string]string{
|
||||
"status": status,
|
||||
"date_start": dateStart,
|
||||
"date_end": dateEnd,
|
||||
}
|
||||
|
||||
h.renderer.VehiclesManagementBookingsList(w, r, result.VehiclesMap, result.DriversMap, result.Bookings, result.CacheID, filters)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesFleetAddHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
// Get group from context
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse form data
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse address JSON
|
||||
var address map[string]any
|
||||
if addressStr := r.FormValue("address"); addressStr != "" {
|
||||
if err := json.Unmarshal([]byte(addressStr), &address); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing address JSON")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Parse other_properties from form fields
|
||||
otherProperties := make(map[string]any)
|
||||
vehicleOptionalFields := h.cfg.Get("modules.fleets.vehicle_optional_fields")
|
||||
log.Debug().Msgf("CREATE: vehicleOptionalFields type: %T, value: %+v", vehicleOptionalFields, vehicleOptionalFields)
|
||||
if fields, ok := vehicleOptionalFields.([]map[string]any); ok {
|
||||
log.Debug().Msgf("CREATE: Successfully cast to []map[string]any, length: %d", len(fields))
|
||||
for _, field := range fields {
|
||||
log.Debug().Any("field", field).Msg("vehicle other properties field handling")
|
||||
if fieldName, ok := field["name"].(string); ok {
|
||||
value := r.FormValue(fieldName)
|
||||
log.Debug().Msgf("CREATE: Field %s: value='%s'", fieldName, value)
|
||||
if value != "" {
|
||||
otherProperties[fieldName] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debug().Msg("CREATE: Failed to cast vehicleOptionalFields to []map[string]any")
|
||||
}
|
||||
log.Debug().Msgf("CREATE: Final otherProperties: %+v", otherProperties)
|
||||
|
||||
vehicleID, err := h.applicationHandler.CreateVehicle(r.Context(),
|
||||
r.FormValue("name"),
|
||||
r.FormValue("type"),
|
||||
r.FormValue("informations"),
|
||||
r.FormValue("licence_plate"),
|
||||
r.FormValue("kilometers"),
|
||||
r.FormValue("automatic") == "on",
|
||||
address,
|
||||
otherProperties,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating vehicle")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", vehicleID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// GET request - show form
|
||||
vehicleTypes, err := h.applicationHandler.GetVehicleTypes(r.Context())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error getting vehicle types")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehiclesFleetAdd(w, r, vehicleTypes)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesFleetDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
vehicleID := vars["vehicleid"]
|
||||
|
||||
result, err := h.applicationHandler.GetVehicleDisplay(r.Context(), vehicleID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicle display")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehiclesFleetDisplay(w, r, result.Vehicle, result.Beneficiaries)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehicleManagementBookingDisplayHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Parse form data
|
||||
r.ParseForm()
|
||||
|
||||
err := h.applicationHandler.UpdateBooking(r.Context(), bookingID,
|
||||
r.FormValue("startdate"),
|
||||
r.FormValue("enddate"),
|
||||
r.FormValue("unavailablefrom"),
|
||||
r.FormValue("unavailableto"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating booking")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result, err := h.applicationHandler.GetBookingDisplay(r.Context(), bookingID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving booking display")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehicleManagementBookingDisplay(w, r, result.Booking, result.Vehicle, result.Beneficiary, result.Group, result.Documents, result.FileTypesMap, result.Alternatives)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehicleManagementBookingChangeVehicleHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
r.ParseForm()
|
||||
newVehicle := r.FormValue("vehicle")
|
||||
|
||||
err := h.applicationHandler.ChangeBookingVehicle(r.Context(), bookingID, newVehicle)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error changing booking vehicle")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/bookings/%s", bookingID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesFleetMakeUnavailableHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get context values
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
log.Error().Msg("no current group")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
u := r.Context().Value(identification.IdtokenKey)
|
||||
if u == nil {
|
||||
log.Error().Msg("no current user")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
currentUserToken := u.(*oidc.IDToken)
|
||||
|
||||
c := r.Context().Value(identification.ClaimsKey)
|
||||
if c == nil {
|
||||
log.Error().Msg("no current user claims")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
currentUserClaims := c.(map[string]any)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
vehicleID := vars["vehicleid"]
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
err := h.applicationHandler.MakeVehicleUnavailable(r.Context(), vehicleID,
|
||||
r.FormValue("unavailablefrom"),
|
||||
r.FormValue("unavailableto"),
|
||||
r.FormValue("comment"),
|
||||
currentUserToken.Subject,
|
||||
currentUserClaims,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error making vehicle unavailable")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", vehicleID), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteBookingHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
// Get form data
|
||||
motif := r.FormValue("motif")
|
||||
|
||||
// Get current user information
|
||||
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
||||
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
||||
currentGroup := r.Context().Value(identification.GroupKey)
|
||||
|
||||
err := h.applicationHandler.UnbookVehicle(r.Context(), bookingID, motif, currentUserToken.Subject, currentUserClaims, currentGroup)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error unbooking vehicle")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/vehicles-management/bookings/", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) UnbookingVehicleHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
bookingID := vars["bookingid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
// Extract user and group info from context
|
||||
currentUserToken := r.Context().Value(identification.IdtokenKey).(*oidc.IDToken)
|
||||
currentUserClaims := r.Context().Value(identification.ClaimsKey).(map[string]any)
|
||||
currentGroup := r.Context().Value(identification.GroupKey)
|
||||
|
||||
motif := r.FormValue("motif")
|
||||
|
||||
err := h.applicationHandler.UnbookVehicle(r.Context(), bookingID, motif,
|
||||
currentUserToken.Subject, currentUserClaims, currentGroup)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error unbooking vehicle")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/vehicles-management/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// GET request - show form
|
||||
booking, err := h.applicationHandler.GetBookingForUnbooking(r.Context(), bookingID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving booking for unbooking")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.UnbookingVehicle(w, r, booking)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) VehiclesFleetUpdateHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
vehicleID := vars["vehicleid"]
|
||||
|
||||
if r.Method == "POST" {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing form")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse address JSON
|
||||
var address map[string]any
|
||||
if addressStr := r.FormValue("address"); addressStr != "" {
|
||||
if err := json.Unmarshal([]byte(addressStr), &address); err != nil {
|
||||
log.Error().Err(err).Msg("error parsing address JSON")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Parse other_properties from form fields
|
||||
otherProperties := make(map[string]any)
|
||||
vehicleOptionalFields := h.cfg.Get("modules.fleets.vehicle_optional_fields")
|
||||
log.Debug().Msgf("UPDATE: vehicleOptionalFields type: %T, value: %+v", vehicleOptionalFields, vehicleOptionalFields)
|
||||
if fields, ok := vehicleOptionalFields.([]any); ok {
|
||||
log.Debug().Msgf("UPDATE: Successfully cast to []map[string]any, length: %d", len(fields))
|
||||
for _, f := range fields {
|
||||
if field, ok := f.(map[string]any); ok {
|
||||
log.Debug().Any("field", field).Msg("vehicle other properties field handling (update)")
|
||||
if fieldName, ok := field["name"].(string); ok {
|
||||
value := r.FormValue(fieldName)
|
||||
log.Debug().Msgf("UPDATE: Field %s: value='%s'", fieldName, value)
|
||||
if value != "" {
|
||||
otherProperties[fieldName] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debug().Msg("UPDATE: Failed to cast vehicleOptionalFields to []map[string]any")
|
||||
}
|
||||
log.Debug().Msgf("UPDATE: Final otherProperties: %+v", otherProperties)
|
||||
|
||||
updatedVehicleID, err := h.applicationHandler.UpdateVehicle(r.Context(), vehicleID,
|
||||
r.FormValue("name"),
|
||||
r.FormValue("type"),
|
||||
r.FormValue("informations"),
|
||||
r.FormValue("licence_plate"),
|
||||
r.FormValue("kilometers"),
|
||||
r.FormValue("automatic") == "on",
|
||||
address,
|
||||
otherProperties,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error updating vehicle")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", updatedVehicleID), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// GET request - show form
|
||||
result, err := h.applicationHandler.GetVehicleForUpdate(r.Context(), vehicleID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error retrieving vehicle for update")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderer.VehiclesFleetUpdate(w, r, result.Vehicle, result.VehicleTypes)
|
||||
}
|
||||
}
|
||||
58
servers/web/application/wallets.go
Normal file
58
servers/web/application/wallets.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) CreditWalletHTTPHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
userid := vars["userid"]
|
||||
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
amountStr := r.FormValue("amount")
|
||||
paymentMethod := r.FormValue("payment_method")
|
||||
description := r.FormValue("description")
|
||||
|
||||
amount, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not read amount")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if paymentMethod == "" {
|
||||
paymentMethod = "Paiement en espèce (MMS)"
|
||||
}
|
||||
|
||||
err = h.applicationHandler.CreditWallet(r.Context(), userid, amount, paymentMethod, description)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not credit wallet")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
refererURL, err := url.Parse(r.Referer())
|
||||
if err != nil {
|
||||
http.Redirect(w, r, r.Referer(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
query := refererURL.Query()
|
||||
query.Set("tab", "wallet")
|
||||
refererURL.RawQuery = query.Encode()
|
||||
|
||||
http.Redirect(w, r, refererURL.String(), http.StatusFound)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user