lot of new functionalities
This commit is contained in:
parent
a6f70a6e85
commit
d992a7984f
|
|
@ -2,3 +2,5 @@
|
||||||
themes/*
|
themes/*
|
||||||
.vscode
|
.vscode
|
||||||
__debug_bin
|
__debug_bin
|
||||||
|
parcoursmob
|
||||||
|
.idea
|
||||||
|
|
|
||||||
51
config.go
51
config.go
|
|
@ -14,7 +14,15 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
"public_dir": "template/default/public",
|
"public_dir": "template/default/public",
|
||||||
},
|
},
|
||||||
"server": map[string]any{
|
"server": map[string]any{
|
||||||
"listen": "0.0.0.0:9000",
|
"listen": "0.0.0.0:9000", // DEPRECATED
|
||||||
|
"web": map[string]any{
|
||||||
|
"enabled": true,
|
||||||
|
"listen": "0.0.0.0:8080",
|
||||||
|
},
|
||||||
|
"mcp": map[string]any{
|
||||||
|
"enabled": false,
|
||||||
|
"listen": "0.0.0.0:8081",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"identification": map[string]any{
|
"identification": map[string]any{
|
||||||
"sessions": map[string]any{
|
"sessions": map[string]any{
|
||||||
|
|
@ -114,9 +122,14 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
},
|
},
|
||||||
"journeys": map[string]any{
|
"journeys": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"search_view": "tabs",
|
||||||
},
|
},
|
||||||
"solidarity_transport": map[string]any{
|
"solidarity_transport": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"pagination": map[string]any{
|
||||||
|
"trips_items_per_page": 10,
|
||||||
|
"drivers_items_per_page": 10,
|
||||||
|
},
|
||||||
"drivers": map[string]any{
|
"drivers": map[string]any{
|
||||||
"documents_types": []string{"membership_form", "driving_licence", "identity_proof", "other"},
|
"documents_types": []string{"membership_form", "driving_licence", "identity_proof", "other"},
|
||||||
"validated_profile": map[string]any{
|
"validated_profile": map[string]any{
|
||||||
|
|
@ -150,9 +163,24 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"booking_motivations": []map[string]string{
|
||||||
|
{"value": "Administratif", "label": "Administratif (trajet garanti)"},
|
||||||
|
{"value": "Commerce", "label": "Commerce"},
|
||||||
|
{"value": "Courses", "label": "Courses"},
|
||||||
|
{"value": "Insertion", "label": "Insertion (trajet garanti)"},
|
||||||
|
{"value": "Loisirs", "label": "Loisirs"},
|
||||||
|
{"value": "Visite à un proche", "label": "Visite à un proche"},
|
||||||
|
{"value": "Santé", "label": "Santé (trajet garanti)"},
|
||||||
|
{"value": "", "label": "Autre "},
|
||||||
|
},
|
||||||
|
"guaranteed_trip_motivations": []string{"Santé", "Insertion", "Administratif"},
|
||||||
},
|
},
|
||||||
"organized_carpool": map[string]any{
|
"organized_carpool": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"pagination": map[string]any{
|
||||||
|
"trips_items_per_page": 10,
|
||||||
|
"drivers_items_per_page": 10,
|
||||||
|
},
|
||||||
"drivers": map[string]any{
|
"drivers": map[string]any{
|
||||||
"documents_types": []string{"membership_form", "driving_licence", "identity_proof", "other"},
|
"documents_types": []string{"membership_form", "driving_licence", "identity_proof", "other"},
|
||||||
"validated_profile": map[string]any{
|
"validated_profile": map[string]any{
|
||||||
|
|
@ -163,6 +191,18 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"booking_motivations": []map[string]string{
|
||||||
|
{"value": "Administratif", "label": "Administratif"},
|
||||||
|
{"value": "Commerce", "label": "Commerce"},
|
||||||
|
{"value": "Courses", "label": "Courses"},
|
||||||
|
{"value": "Insertion", "label": "Insertion"},
|
||||||
|
{"value": "Loisirs", "label": "Loisirs"},
|
||||||
|
{"value": "Travail", "label": "Travail"},
|
||||||
|
{"value": "Formation", "label": "Formation"},
|
||||||
|
{"value": "Visite à un proche", "label": "Visite à un proche"},
|
||||||
|
{"value": "Santé", "label": "Santé"},
|
||||||
|
{"value": "", "label": "Autre "},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"vehicles": map[string]any{
|
"vehicles": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
@ -170,6 +210,10 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
"vehicles_management": map[string]any{
|
"vehicles_management": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
|
"fleets": map[string]any{
|
||||||
|
"vehicle_types": []string{"Voiture", "Voiture sans permis", "Scooter", "Trotinette", "Vélo électrique"},
|
||||||
|
"vehicle_optional_fields": []map[string]any{},
|
||||||
|
},
|
||||||
"agenda": map[string]any{
|
"agenda": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
|
|
@ -178,6 +222,7 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
},
|
},
|
||||||
"support": map[string]any{
|
"support": map[string]any{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"email": "support@mobicoop.fr",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"geo": map[string]any{
|
"geo": map[string]any{
|
||||||
|
|
@ -208,6 +253,10 @@ func ReadConfig() (*viper.Viper, error) {
|
||||||
"communes": "https://etalab-datasets.geo.data.gouv.fr/contours-administratifs/latest/geojson/communes-50m.geojson",
|
"communes": "https://etalab-datasets.geo.data.gouv.fr/contours-administratifs/latest/geojson/communes-50m.geojson",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"filters": map[string]any{
|
||||||
|
"enabled": false,
|
||||||
|
"geographies": []map[string]string{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,658 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||||
|
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdministrationDataResult struct {
|
||||||
|
Accounts []mobilityaccountsstorage.Account
|
||||||
|
Beneficiaries []mobilityaccountsstorage.Account
|
||||||
|
Groups []groupstorage.Group
|
||||||
|
Bookings []fleetsstorage.Booking
|
||||||
|
Events []agendastorage.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminVehiclesStatsResult struct {
|
||||||
|
Vehicles []fleetsstorage.Vehicle
|
||||||
|
Bookings []fleetsstorage.Booking
|
||||||
|
Groups map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminBookingsStatsResult struct {
|
||||||
|
Vehicles map[string]fleetsstorage.Vehicle
|
||||||
|
Bookings []fleetsstorage.Booking
|
||||||
|
Groups map[string]any
|
||||||
|
BeneficiariesMap map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminBeneficiariesStatsResult struct {
|
||||||
|
Beneficiaries []mobilityaccountsstorage.Account
|
||||||
|
CacheID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminEventsStatsResult struct {
|
||||||
|
Events []agendastorage.Event
|
||||||
|
Groups map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdministrationData retrieves all data needed for the administration dashboard
|
||||||
|
func (h *ApplicationHandler) GetAdministrationData(ctx context.Context) (*AdministrationDataResult, error) {
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
accounts, beneficiaries []mobilityaccountsstorage.Account
|
||||||
|
bookings []fleetsstorage.Booking
|
||||||
|
accountsErr, beneficiariesErr, bookingsErr, groupsResponseErr, eventsResponseErr, groupsBatchErr error
|
||||||
|
groups = []groupstorage.Group{}
|
||||||
|
responses = []agendastorage.Event{}
|
||||||
|
groupsResponse *groupsmanagement.GetGroupsResponse
|
||||||
|
eventsResponse *agenda.GetEventsResponse
|
||||||
|
groupids = []string{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Retrieve accounts in a goroutine
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
accounts, accountsErr = h.services.GetAccounts()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Retrieve beneficiaries in a goroutine
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
beneficiaries, beneficiariesErr = h.services.GetBeneficiaries()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Retrieve bookings in a goroutine
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
bookings, bookingsErr = h.services.GetBookings()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Retrieve groups in a goroutine
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
request := &groupsmanagement.GetGroupsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_organizations"},
|
||||||
|
}
|
||||||
|
groupsResponse, groupsResponseErr = h.services.GRPC.GroupsManagement.GetGroups(ctx, request)
|
||||||
|
if groupsResponseErr == nil {
|
||||||
|
for _, group := range groupsResponse.Groups {
|
||||||
|
g := group.ToStorageType()
|
||||||
|
groups = append(groups, g)
|
||||||
|
}
|
||||||
|
sort.Sort(sorting.GroupsByName(groups))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Retrieve Events in a goroutine
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
eventsResponse, eventsResponseErr = h.services.GRPC.Agenda.GetEvents(ctx, &agenda.GetEventsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_dispositifs"},
|
||||||
|
})
|
||||||
|
if eventsResponseErr == nil {
|
||||||
|
for _, e := range eventsResponse.Events {
|
||||||
|
groupids = append(groupids, e.Owners...)
|
||||||
|
responses = append(responses, e.ToStorageType())
|
||||||
|
}
|
||||||
|
sort.Sort(sorting.EventsByStartdate(responses))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Check for errors
|
||||||
|
if accountsErr != nil || beneficiariesErr != nil || bookingsErr != nil || groupsResponseErr != nil || eventsResponseErr != nil {
|
||||||
|
log.Error().
|
||||||
|
Any("accounts error", accountsErr).
|
||||||
|
Any("beneficiaries error", beneficiariesErr).
|
||||||
|
Any("bookings error", bookingsErr).
|
||||||
|
Any("groups response error", groupsResponseErr).
|
||||||
|
Any("events response error", eventsResponseErr).
|
||||||
|
Any("groups batch error", groupsBatchErr).
|
||||||
|
Msg("Error in retrieving administration data")
|
||||||
|
return nil, fmt.Errorf("error retrieving administration data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AdministrationDataResult{
|
||||||
|
Accounts: accounts,
|
||||||
|
Beneficiaries: beneficiaries,
|
||||||
|
Groups: groups,
|
||||||
|
Bookings: bookings,
|
||||||
|
Events: responses,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAdministrationGroup creates a new administration group
|
||||||
|
func (h *ApplicationHandler) CreateAdministrationGroup(ctx context.Context, name string, modules map[string]any) (string, error) {
|
||||||
|
groupid := uuid.NewString()
|
||||||
|
|
||||||
|
dataMap := map[string]any{
|
||||||
|
"name": name,
|
||||||
|
"modules": modules,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot create PB struct from data map")
|
||||||
|
return "", fmt.Errorf("failed to create group data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request_organization := &groupsmanagement.AddGroupRequest{
|
||||||
|
Group: &groupsmanagement.Group{
|
||||||
|
Id: groupid,
|
||||||
|
Namespace: "parcoursmob_organizations",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
request_role := &groupsmanagement.AddGroupRequest{
|
||||||
|
Group: &groupsmanagement.Group{
|
||||||
|
Id: groupid + ":admin",
|
||||||
|
Namespace: "parcoursmob_roles",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create organization group
|
||||||
|
_, err = h.services.GRPC.GroupsManagement.AddGroup(ctx, request_organization)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Issue in Groups management service - AddGroup")
|
||||||
|
return "", fmt.Errorf("failed to create organization group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create admin role for the organization
|
||||||
|
_, err = h.services.GRPC.GroupsManagement.AddGroup(ctx, request_role)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Issue in Groups management service - AddGroup")
|
||||||
|
return "", fmt.Errorf("failed to create admin role: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdministrationGroupDataResult struct {
|
||||||
|
Group groupstorage.Group
|
||||||
|
Members []mobilityaccountsstorage.Account
|
||||||
|
Admins []mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdministrationGroupData retrieves data for a specific administration group
|
||||||
|
func (h *ApplicationHandler) GetAdministrationGroupData(ctx context.Context, groupID string) (*AdministrationGroupDataResult, error) {
|
||||||
|
request := &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: groupID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Issue in Groups management service - GetGroup")
|
||||||
|
return nil, fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupmembers, admins, err := h.groupmembers(groupID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("issue retrieving group members")
|
||||||
|
return nil, fmt.Errorf("failed to get group members: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AdministrationGroupDataResult{
|
||||||
|
Group: resp.Group.ToStorageType(),
|
||||||
|
Members: groupmembers,
|
||||||
|
Admins: admins,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteAdministrationGroupAdmin invites a user as admin to an administration group
|
||||||
|
func (h *ApplicationHandler) InviteAdministrationGroupAdmin(ctx context.Context, groupID, username string) error {
|
||||||
|
// Get group info
|
||||||
|
groupResp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: groupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group := groupResp.Group.ToStorageType()
|
||||||
|
|
||||||
|
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(ctx, &mobilityaccounts.GetAccountUsernameRequest{
|
||||||
|
Username: username,
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Account already exists: adding the existing account to group as admin
|
||||||
|
account := accountresp.Account.ToStorageType()
|
||||||
|
if account.Data["groups"] == nil {
|
||||||
|
account.Data["groups"] = []any{}
|
||||||
|
}
|
||||||
|
account.Data["groups"] = append(account.Data["groups"].([]any), groupID+":admin")
|
||||||
|
|
||||||
|
as, err := mobilityaccounts.AccountFromStorageType(&account)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, &mobilityaccounts.UpdateDataRequest{
|
||||||
|
Account: as,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"group": group.Data["name"],
|
||||||
|
"baseUrl": h.config.GetString("base_url"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("onboarding.existing_administrator", username, data); err != nil {
|
||||||
|
log.Warn().Err(err).Msg("failed to send existing admin email")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create onboarding for new admin
|
||||||
|
onboarding := map[string]any{
|
||||||
|
"username": username,
|
||||||
|
"group": groupID,
|
||||||
|
"admin": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return fmt.Errorf("failed to generate random key: %w", err)
|
||||||
|
}
|
||||||
|
key := base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
h.cache.PutWithTTL("onboarding/"+key, onboarding, 72*time.Hour)
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"group": group.Data["name"],
|
||||||
|
"key": key,
|
||||||
|
"baseUrl": h.config.GetString("base_url"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("onboarding.new_administrator", username, data); err != nil {
|
||||||
|
return fmt.Errorf("failed to send new admin email: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteAdministrationGroupMember invites a user as member to an administration group
|
||||||
|
func (h *ApplicationHandler) InviteAdministrationGroupMember(ctx context.Context, groupID, username string) error {
|
||||||
|
// Get group info
|
||||||
|
groupResp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: groupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group := groupResp.Group.ToStorageType()
|
||||||
|
|
||||||
|
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(ctx, &mobilityaccounts.GetAccountUsernameRequest{
|
||||||
|
Username: username,
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Account already exists: adding the existing account to group
|
||||||
|
account := accountresp.Account.ToStorageType()
|
||||||
|
if account.Data["groups"] == nil {
|
||||||
|
account.Data["groups"] = []any{}
|
||||||
|
}
|
||||||
|
account.Data["groups"] = append(account.Data["groups"].([]any), groupID)
|
||||||
|
|
||||||
|
as, err := mobilityaccounts.AccountFromStorageType(&account)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, &mobilityaccounts.UpdateDataRequest{
|
||||||
|
Account: as,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"group": group.Data["name"],
|
||||||
|
"baseUrl": h.config.GetString("base_url"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("onboarding.existing_member", username, data); err != nil {
|
||||||
|
log.Warn().Err(err).Msg("failed to send existing member email")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create onboarding for new member
|
||||||
|
onboarding := map[string]any{
|
||||||
|
"username": username,
|
||||||
|
"group": groupID,
|
||||||
|
"admin": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return fmt.Errorf("failed to generate random key: %w", err)
|
||||||
|
}
|
||||||
|
key := base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
h.cache.PutWithTTL("onboarding/"+key, onboarding, 72*time.Hour)
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"group": group.Data["name"],
|
||||||
|
"key": key,
|
||||||
|
"baseUrl": h.config.GetString("base_url"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("onboarding.new_member", username, data); err != nil {
|
||||||
|
return fmt.Errorf("failed to send new member email: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filteVehicle(r *http.Request, v *fleets.Vehicle) bool {
|
||||||
|
g := r.Context().Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
for _, n := range v.Administrators {
|
||||||
|
if n == group.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehiclesStats() (*AdminVehiclesStatsResult, error) {
|
||||||
|
bookings := []fleetsstorage.Booking{}
|
||||||
|
administrators := []string{}
|
||||||
|
request := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicles := []fleetsstorage.Vehicle{}
|
||||||
|
for _, vehicle := range resp.Vehicles {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
adminfound := false
|
||||||
|
for _, a := range administrators {
|
||||||
|
if len(v.Administrators) > 0 && a == v.Administrators[0] {
|
||||||
|
adminfound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !adminfound && len(v.Administrators) > 0 {
|
||||||
|
administrators = append(administrators, v.Administrators[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicleBookings := []fleetsstorage.Booking{}
|
||||||
|
for _, b := range v.Bookings {
|
||||||
|
if b.Unavailableto.After(time.Now()) {
|
||||||
|
vehicleBookings = append(vehicleBookings, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Bookings = vehicleBookings
|
||||||
|
vehicles = append(vehicles, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := map[string]any{}
|
||||||
|
if len(administrators) > 0 {
|
||||||
|
admingroups, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: administrators,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range admingroups.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.VehiclesByLicencePlate(vehicles))
|
||||||
|
sort.Sort(sorting.BookingsByStartdate(bookings))
|
||||||
|
|
||||||
|
return &AdminVehiclesStatsResult{
|
||||||
|
Vehicles: vehicles,
|
||||||
|
Bookings: bookings,
|
||||||
|
Groups: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBookingsStats(status, startDate, endDate string) (*AdminBookingsStatsResult, error) {
|
||||||
|
vehicles := map[string]fleetsstorage.Vehicle{}
|
||||||
|
bookings := []fleetsstorage.Booking{}
|
||||||
|
|
||||||
|
// Parse start date filter
|
||||||
|
var startdate time.Time
|
||||||
|
if startDate != "" {
|
||||||
|
if parsed, err := time.Parse("2006-01-02", startDate); err == nil {
|
||||||
|
startdate = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse end date filter
|
||||||
|
var enddate time.Time
|
||||||
|
if endDate != "" {
|
||||||
|
if parsed, err := time.Parse("2006-01-02", endDate); err == nil {
|
||||||
|
enddate = parsed.Add(24 * time.Hour) // End of day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
IncludeDeleted: true,
|
||||||
|
}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries_ids := []string{}
|
||||||
|
|
||||||
|
for _, vehicle := range resp.Vehicles {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
|
||||||
|
for _, b := range v.Bookings {
|
||||||
|
// Apply status filter
|
||||||
|
if status != "" {
|
||||||
|
bookingStatus := b.Status()
|
||||||
|
statusInt := 0
|
||||||
|
|
||||||
|
if b.Deleted {
|
||||||
|
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
||||||
|
} else {
|
||||||
|
statusInt = bookingStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map status string to int
|
||||||
|
var filterStatusInt int
|
||||||
|
switch status {
|
||||||
|
case "FORTHCOMING":
|
||||||
|
filterStatusInt = 1
|
||||||
|
case "ONGOING":
|
||||||
|
filterStatusInt = 0
|
||||||
|
case "TERMINATED":
|
||||||
|
filterStatusInt = -1
|
||||||
|
case "CANCELLED":
|
||||||
|
filterStatusInt = -2
|
||||||
|
default:
|
||||||
|
filterStatusInt = 999 // Invalid status, won't match anything
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusInt != filterStatusInt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply date filter (on startdate)
|
||||||
|
if !startdate.IsZero() && b.Startdate.Before(startdate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bookings = append(bookings, b)
|
||||||
|
beneficiaries_ids = append(beneficiaries_ids, b.Driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicles[v.ID] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := map[string]any{}
|
||||||
|
|
||||||
|
admingroups, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), &groupsmanagement.GetGroupsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_organizations"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range admingroups.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: beneficiaries_ids,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries_map := map[string]any{}
|
||||||
|
for _, ben := range beneficiaries.Accounts {
|
||||||
|
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AdminBookingsStatsResult{
|
||||||
|
Vehicles: vehicles,
|
||||||
|
Bookings: bookings,
|
||||||
|
Groups: groups,
|
||||||
|
BeneficiariesMap: beneficiaries_map,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBeneficiariesStats() (*AdminBeneficiariesStatsResult, error) {
|
||||||
|
beneficiaries, err := h.services.GetBeneficiaries()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheid := uuid.New().String()
|
||||||
|
h.cache.Put(cacheid, beneficiaries)
|
||||||
|
|
||||||
|
return &AdminBeneficiariesStatsResult{
|
||||||
|
Beneficiaries: beneficiaries,
|
||||||
|
CacheID: cacheid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetEventsStats() (*AdminEventsStatsResult, error) {
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_dispositifs"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := []agendastorage.Event{}
|
||||||
|
groupids := []string{}
|
||||||
|
|
||||||
|
for _, event := range resp.Events {
|
||||||
|
responses = append(responses, event.ToStorageType())
|
||||||
|
groupids = append(groupids, event.Owners...)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsResponse, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: groupids,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := map[string]any{}
|
||||||
|
for _, g := range groupsResponse.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AdminEventsStatsResult{
|
||||||
|
Events: responses,
|
||||||
|
Groups: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) members() ([]*accounts.Account, error) {
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(context.TODO(), &accounts.GetAccountsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) groupmembers(groupid string) (groupmembers []mobilityaccountsstorage.Account, admins []mobilityaccountsstorage.Account, err error) {
|
||||||
|
members, err := h.members()
|
||||||
|
if err != nil {
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot get members")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupmembers = []mobilityaccountsstorage.Account{}
|
||||||
|
admins = []mobilityaccountsstorage.Account{}
|
||||||
|
|
||||||
|
for _, m := range members {
|
||||||
|
mm := m.ToStorageType()
|
||||||
|
for _, g := range mm.Data["groups"].([]any) {
|
||||||
|
if g.(string) == groupid {
|
||||||
|
groupmembers = append(groupmembers, mm)
|
||||||
|
}
|
||||||
|
if g.(string) == groupid+":admin" {
|
||||||
|
admins = append(admins, mm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupmembers, admins, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,602 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||||
|
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
ics "github.com/arran4/golang-ical"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type AgendaEventsResult struct {
|
||||||
|
Events []agendastorage.Event
|
||||||
|
Groups map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetAgendaEvents(ctx context.Context, minDate, maxDate *time.Time) (*AgendaEventsResult, error) {
|
||||||
|
request := &agenda.GetEventsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_dispositifs"},
|
||||||
|
}
|
||||||
|
|
||||||
|
if minDate != nil {
|
||||||
|
request.Mindate = timestamppb.New(*minDate)
|
||||||
|
}
|
||||||
|
if maxDate != nil {
|
||||||
|
request.Maxdate = timestamppb.New(*maxDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvents(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := []agendastorage.Event{}
|
||||||
|
groupids := []string{}
|
||||||
|
for _, e := range resp.Events {
|
||||||
|
groupids = append(groupids, e.Owners...)
|
||||||
|
responses = append(responses, e.ToStorageType())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.EventsByStartdate(responses))
|
||||||
|
|
||||||
|
groups := map[string]any{}
|
||||||
|
if len(groupids) > 0 {
|
||||||
|
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(ctx, &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: groupids,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
for _, g := range groupsresp.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AgendaEventsResult{
|
||||||
|
Events: responses,
|
||||||
|
Groups: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) CreateAgendaEvent(ctx context.Context, name, eventType, description string, address any, allday bool, startdate, enddate *time.Time, starttime, endtime string, maxSubscribers int, file io.Reader, filename string, fileSize int64, documentType, documentName string) (string, error) {
|
||||||
|
// Get current group
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return "", fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
data, _ := structpb.NewStruct(map[string]any{
|
||||||
|
"address": address,
|
||||||
|
})
|
||||||
|
|
||||||
|
request := &agenda.CreateEventRequest{
|
||||||
|
Event: &agenda.Event{
|
||||||
|
Namespace: "parcoursmob_dispositifs",
|
||||||
|
Owners: []string{group.ID},
|
||||||
|
Type: eventType,
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
Startdate: timestamppb.New(*startdate),
|
||||||
|
Enddate: timestamppb.New(*enddate),
|
||||||
|
Starttime: starttime,
|
||||||
|
Endtime: endtime,
|
||||||
|
Allday: allday,
|
||||||
|
MaxSubscribers: int64(maxSubscribers),
|
||||||
|
Data: data,
|
||||||
|
Deleted: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.CreateEvent(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle file upload if provided
|
||||||
|
if file != nil && filename != "" {
|
||||||
|
fileid := uuid.NewString()
|
||||||
|
|
||||||
|
metadata := map[string]string{
|
||||||
|
"file_type": documentType,
|
||||||
|
"file_name": documentName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.filestorage.Put(file, filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s_%s", resp.Event.Id, fileid, filename), fileSize, metadata); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Event.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type AgendaEventResult struct {
|
||||||
|
Event agendastorage.Event
|
||||||
|
Group storage.Group
|
||||||
|
Documents []filestorage.FileInfo
|
||||||
|
Subscribers map[string]any
|
||||||
|
Accounts []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetAgendaEvent(ctx context.Context, eventID string) (*AgendaEventResult, error) {
|
||||||
|
request := &agenda.GetEventRequest{
|
||||||
|
Id: eventID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
grouprequest := &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: resp.Event.Owners[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribers := map[string]any{}
|
||||||
|
accids := []string{}
|
||||||
|
for _, v := range resp.Event.Subscriptions {
|
||||||
|
accids = append(accids, v.Subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(accids) > 0 {
|
||||||
|
subscriberresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
||||||
|
ctx,
|
||||||
|
&mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: accids,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
for _, sub := range subscriberresp.Accounts {
|
||||||
|
subscribers[sub.Id] = sub.ToStorageType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return nil, fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
accountids := []string{}
|
||||||
|
for _, m := range group.Members {
|
||||||
|
if !contains(resp.Event.Subscriptions, m) {
|
||||||
|
accountids = append(accountids, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts := []any{}
|
||||||
|
if len(accountids) > 0 {
|
||||||
|
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
||||||
|
ctx,
|
||||||
|
&mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: accountids,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
for _, acc := range accountresp.Accounts {
|
||||||
|
accounts = append(accounts, acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
documents := h.filestorage.List(filestorage.PREFIX_AGENDA + "/" + eventID)
|
||||||
|
|
||||||
|
return &AgendaEventResult{
|
||||||
|
Event: resp.Event.ToStorageType(),
|
||||||
|
Group: groupresp.Group.ToStorageType(),
|
||||||
|
Documents: documents,
|
||||||
|
Subscribers: subscribers,
|
||||||
|
Accounts: accounts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) SubscribeToAgendaEvent(ctx context.Context, eventID, subscriber string, subscriptionData map[string]any) error {
|
||||||
|
datapb, err := structpb.NewStruct(subscriptionData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &agenda.SubscribeEventRequest{
|
||||||
|
Eventid: eventID,
|
||||||
|
Subscriber: subscriber,
|
||||||
|
Data: datapb,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Agenda.SubscribeEvent(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UnsubscribeFromAgendaEvent(ctx context.Context, eventID, subscribeID, motif, currentUserID, currentUserDisplayName, currentUserEmail, currentGroupID, currentGroupName string) error {
|
||||||
|
// Get the event first
|
||||||
|
request := &agenda.GetEventRequest{
|
||||||
|
Id: eventID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find subscription data for the subscriber being removed
|
||||||
|
var s_b_id, s_b_name, s_b_email, s_b_group_id, s_b_group_name string
|
||||||
|
for i := range resp.Event.Subscriptions {
|
||||||
|
if resp.Event.Subscriptions[i].Subscriber == subscribeID {
|
||||||
|
s_b_id = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["id"].GetStringValue()
|
||||||
|
s_b_name = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["display_name"].GetStringValue()
|
||||||
|
s_b_email = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["email"].GetStringValue()
|
||||||
|
s_b_group_id = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["group"].GetStructValue().Fields["id"].GetStringValue()
|
||||||
|
s_b_group_name = resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["group"].GetStructValue().Fields["name"].GetStringValue()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"subscribed_by": map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"id": s_b_id,
|
||||||
|
"display_name": s_b_name,
|
||||||
|
"email": s_b_email,
|
||||||
|
},
|
||||||
|
"group": map[string]any{
|
||||||
|
"id": s_b_group_id,
|
||||||
|
"name": s_b_group_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"unsubscribed_by": map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"id": currentUserID,
|
||||||
|
"display_name": currentUserDisplayName,
|
||||||
|
"email": currentUserEmail,
|
||||||
|
},
|
||||||
|
"group": map[string]any{
|
||||||
|
"id": currentGroupID,
|
||||||
|
"name": currentGroupName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"motif": motif,
|
||||||
|
}
|
||||||
|
|
||||||
|
datapb, err := structpb.NewStruct(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRequest := &agenda.DeleteSubscriptionRequest{
|
||||||
|
Subscriber: subscribeID,
|
||||||
|
Eventid: eventID,
|
||||||
|
Data: datapb,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Agenda.DeleteSubscription(ctx, deleteRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email notification
|
||||||
|
emailData := map[string]any{
|
||||||
|
"motif": motif,
|
||||||
|
"user": currentUserDisplayName,
|
||||||
|
"subscriber": fmt.Sprintf("http://localhost:9000/app/beneficiaries/%s", subscribeID),
|
||||||
|
"link": fmt.Sprintf("http://localhost:9000/app/agenda/%s", eventID),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("delete_subscriber.request", s_b_email, emailData); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot send email")
|
||||||
|
// Don't return error for email failure
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgendaEventHistoryResult struct {
|
||||||
|
Event agendastorage.Event
|
||||||
|
Group storage.Group
|
||||||
|
Subscribers map[string]any
|
||||||
|
Accounts []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetAgendaEventHistory(ctx context.Context, eventID string) (*AgendaEventHistoryResult, error) {
|
||||||
|
request := &agenda.GetEventRequest{
|
||||||
|
Id: eventID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
grouprequest := &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: resp.Event.Owners[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribers := map[string]any{}
|
||||||
|
|
||||||
|
accids := []string{}
|
||||||
|
for _, v := range resp.Event.DeletedSubscription {
|
||||||
|
accids = append(accids, v.Subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(accids) > 0 {
|
||||||
|
subscriberresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
||||||
|
ctx,
|
||||||
|
&mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: accids,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
for _, sub := range subscriberresp.Accounts {
|
||||||
|
subscribers[sub.Id] = sub.ToStorageType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return nil, fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
accountids := []string{}
|
||||||
|
for _, m := range group.Members {
|
||||||
|
if !contains(resp.Event.DeletedSubscription, m) {
|
||||||
|
accountids = append(accountids, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts := []any{}
|
||||||
|
if len(accountids) > 0 {
|
||||||
|
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
||||||
|
ctx,
|
||||||
|
&mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: accountids,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
for _, acc := range accountresp.Accounts {
|
||||||
|
accounts = append(accounts, acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AgendaEventHistoryResult{
|
||||||
|
Event: resp.Event.ToStorageType(),
|
||||||
|
Group: groupresp.Group.ToStorageType(),
|
||||||
|
Subscribers: subscribers,
|
||||||
|
Accounts: accounts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) AddEventDocument(ctx context.Context, eventID string, file io.Reader, filename string, fileSize int64, documentType, documentName string) error {
|
||||||
|
fileid := uuid.NewString()
|
||||||
|
|
||||||
|
metadata := map[string]string{
|
||||||
|
"type": documentType,
|
||||||
|
"name": documentName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.filestorage.Put(file, filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s_%s", eventID, fileid, filename), fileSize, metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetEventDocument(ctx context.Context, eventID, document string) (io.Reader, *filestorage.FileInfo, error) {
|
||||||
|
file, info, err := h.filestorage.Get(filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s", eventID, document))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func contains(s []*agenda.Subscription, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a.Subscriber == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UpdateAgendaEvent(ctx context.Context, eventID, name, eventType, description string, address any, allday bool, startdate, enddate *time.Time, starttime, endtime string, maxSubscribers int) (string, error) {
|
||||||
|
// Get current group
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return "", fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
// Get existing event first
|
||||||
|
getRequest := &agenda.GetEventRequest{
|
||||||
|
Id: eventID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := structpb.NewStruct(map[string]any{
|
||||||
|
"address": address,
|
||||||
|
})
|
||||||
|
|
||||||
|
request := &agenda.UpdateEventRequest{
|
||||||
|
Event: &agenda.Event{
|
||||||
|
Namespace: "parcoursmob_dispositifs",
|
||||||
|
Id: eventID,
|
||||||
|
Owners: []string{group.ID},
|
||||||
|
Type: eventType,
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
Startdate: timestamppb.New(*startdate),
|
||||||
|
Enddate: timestamppb.New(*enddate),
|
||||||
|
Starttime: starttime,
|
||||||
|
Endtime: endtime,
|
||||||
|
Allday: allday,
|
||||||
|
MaxSubscribers: int64(maxSubscribers),
|
||||||
|
Data: data,
|
||||||
|
Subscriptions: resp.Event.Subscriptions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
updateResp, err := h.services.GRPC.Agenda.UpdateEvent(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateResp.Event.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) DeleteAgendaEvent(ctx context.Context, eventID string) error {
|
||||||
|
request := &agenda.GetEventRequest{
|
||||||
|
Id: eventID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvent(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRequest := &agenda.UpdateEventRequest{
|
||||||
|
Event: &agenda.Event{
|
||||||
|
Namespace: resp.Event.Namespace,
|
||||||
|
Id: resp.Event.Id,
|
||||||
|
Owners: resp.Event.Owners,
|
||||||
|
Type: resp.Event.Type,
|
||||||
|
Name: resp.Event.Name,
|
||||||
|
Description: resp.Event.Description,
|
||||||
|
Startdate: resp.Event.Startdate,
|
||||||
|
Enddate: resp.Event.Enddate,
|
||||||
|
Starttime: resp.Event.Starttime,
|
||||||
|
Endtime: resp.Event.Endtime,
|
||||||
|
Allday: resp.Event.Allday,
|
||||||
|
MaxSubscribers: int64(resp.Event.MaxSubscribers),
|
||||||
|
Data: resp.Event.Data,
|
||||||
|
Subscriptions: resp.Event.Subscriptions,
|
||||||
|
Deleted: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Agenda.UpdateEvent(ctx, updateRequest)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type CalendarResult struct {
|
||||||
|
CalendarData string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GenerateGlobalCalendar(ctx context.Context) (*CalendarResult, error) {
|
||||||
|
events, err := h.services.GetAgendaEvents()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error retrieving agenda events")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar, err := h.icsCalendar(events)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CalendarResult{
|
||||||
|
CalendarData: calendar.Serialize(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GenerateOrganizationCalendar(ctx context.Context, groupID string) (*CalendarResult, error) {
|
||||||
|
events, err := h.services.GetAgendaEvents()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error retrieving agenda events")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredEvents := []services.AgendaEvent{}
|
||||||
|
for _, e := range events {
|
||||||
|
for _, g := range e.Owners {
|
||||||
|
if g == groupID {
|
||||||
|
filteredEvents = append(filteredEvents, e)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar, err := h.icsCalendar(filteredEvents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CalendarResult{
|
||||||
|
CalendarData: calendar.Serialize(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) icsCalendar(events []services.AgendaEvent) (*ics.Calendar, error) {
|
||||||
|
calendar := ics.NewCalendarFor(h.config.GetString("service_name"))
|
||||||
|
|
||||||
|
for _, e := range events {
|
||||||
|
vevent := ics.NewEvent(e.ID)
|
||||||
|
vevent.SetSummary(e.Name)
|
||||||
|
vevent.SetDescription(e.Description)
|
||||||
|
if e.Allday {
|
||||||
|
vevent.SetAllDayStartAt(e.Startdate)
|
||||||
|
if e.Enddate.After(e.Startdate) {
|
||||||
|
vevent.SetAllDayEndAt(e.Enddate.Add(24 * time.Hour))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timeloc, err := time.LoadLocation("Europe/Paris")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Tried to load timezone location Europe/Paris. Error. Missing zones in container ?")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vevent.SetStartAt(e.Startdate.In(timeloc))
|
||||||
|
vevent.SetEndAt(e.Enddate.In(timeloc))
|
||||||
|
}
|
||||||
|
calendar.AddVEvent(vevent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return calendar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,10 +4,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
cache "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
"git.coopgo.io/coopgo-platform/emailing"
|
"git.coopgo.io/coopgo-platform/emailing"
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
|
@ -16,29 +15,24 @@ import (
|
||||||
|
|
||||||
type ApplicationHandler struct {
|
type ApplicationHandler struct {
|
||||||
config *viper.Viper
|
config *viper.Viper
|
||||||
Renderer *renderer.Renderer
|
|
||||||
services *services.ServicesHandler
|
services *services.ServicesHandler
|
||||||
cache cache.CacheHandler
|
cache cache.CacheHandler
|
||||||
filestorage cache.FileStorage
|
filestorage cache.FileStorage
|
||||||
emailing *emailing.Mailer
|
emailing *emailing.Mailer
|
||||||
|
idp *identification.IdentificationProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cache cache.CacheHandler, filestorage cache.FileStorage, emailing *emailing.Mailer) (*ApplicationHandler, error) {
|
func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cache cache.CacheHandler, filestorage cache.FileStorage, emailing *emailing.Mailer, idp *identification.IdentificationProvider) (*ApplicationHandler, error) {
|
||||||
templates_root := cfg.GetString("templates.root")
|
|
||||||
renderer := renderer.NewRenderer(cfg, templates_root, filestorage)
|
|
||||||
return &ApplicationHandler{
|
return &ApplicationHandler{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
Renderer: renderer,
|
|
||||||
services: svc,
|
services: svc,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
filestorage: filestorage,
|
filestorage: filestorage,
|
||||||
emailing: emailing,
|
emailing: emailing,
|
||||||
|
idp: idp,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) NotFound(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) templateFile(file string) string {
|
func (h *ApplicationHandler) templateFile(file string) string {
|
||||||
return h.config.GetString("templates.root") + file
|
return h.config.GetString("templates.root") + file
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
ma "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OAuth2CallbackResult struct {
|
||||||
|
RedirectURL string
|
||||||
|
IDToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ProcessOAuth2Callback(code string, redirectSession string) (*OAuth2CallbackResult, error) {
|
||||||
|
oauth2Token, err := h.idp.OAuth2Config.Exchange(context.Background(), code)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Exchange error")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the ID Token from OAuth2 token.
|
||||||
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
log.Error().Msg("Cannot retrieve ID token")
|
||||||
|
return nil, errors.New("cannot retrieve ID token")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.idp.TokenVerifier.Verify(context.Background(), rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Not able to verify token")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect := "/app/"
|
||||||
|
if redirectSession != "" {
|
||||||
|
redirect = redirectSession
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OAuth2CallbackResult{
|
||||||
|
RedirectURL: redirect,
|
||||||
|
IDToken: rawIDToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LostPasswordInitResult struct {
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) InitiateLostPassword(email string) (*LostPasswordInitResult, error) {
|
||||||
|
account, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &mobilityaccounts.GetAccountUsernameRequest{
|
||||||
|
Username: email,
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key := base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
passwordretrieval := map[string]any{
|
||||||
|
"username": email,
|
||||||
|
"account_id": account.Account.Id,
|
||||||
|
"key": key,
|
||||||
|
}
|
||||||
|
|
||||||
|
h.cache.PutWithTTL("retrieve-password/"+key, passwordretrieval, 72*time.Hour)
|
||||||
|
|
||||||
|
if err := h.emailing.Send("auth.retrieve_password", email, passwordretrieval); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LostPasswordInitResult{Success: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LostPasswordRecoverResult struct {
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) RecoverLostPassword(key, newPassword string) (*LostPasswordRecoverResult, error) {
|
||||||
|
recover, err := h.cache.Get("retrieve-password/" + key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newPassword == "" {
|
||||||
|
return nil, errors.New("password is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.ChangePassword(context.TODO(), &mobilityaccounts.ChangePasswordRequest{
|
||||||
|
Id: recover.(map[string]any)["account_id"].(string),
|
||||||
|
Password: newPassword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cache.Delete("retrieve-password/" + key)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to delete password recovery key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LostPasswordRecoverResult{Success: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetPasswordRecoveryData(key string) (map[string]any, error) {
|
||||||
|
recover, err := h.cache.Get("retrieve-password/" + key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return recover.(map[string]any), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnboardingResult struct {
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) CompleteOnboarding(key, password, firstName, lastName string) (*OnboardingResult, error) {
|
||||||
|
onboarding, err := h.cache.Get("onboarding/" + key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
onboardingmap := onboarding.(map[string]any)
|
||||||
|
|
||||||
|
if password == "" {
|
||||||
|
return nil, errors.New("password is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := []string{
|
||||||
|
onboardingmap["group"].(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if onboardingmap["admin"].(bool) {
|
||||||
|
groups = append(groups, onboardingmap["group"].(string)+":admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
display_name := firstName + " " + lastName
|
||||||
|
account := &ma.Account{
|
||||||
|
Authentication: ma.AccountAuth{
|
||||||
|
Local: ma.LocalAuth{
|
||||||
|
Username: onboardingmap["username"].(string),
|
||||||
|
Password: password,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
Data: map[string]any{
|
||||||
|
"display_name": display_name,
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
"email": onboardingmap["username"],
|
||||||
|
"groups": groups,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := mobilityaccounts.AccountFromStorageType(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &mobilityaccounts.RegisterRequest{
|
||||||
|
Account: acc,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cache.Delete("onboarding/" + key)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to delete onboarding key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OnboardingResult{Success: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetOnboardingData(key string) (map[string]any, error) {
|
||||||
|
onboarding, err := h.cache.Get("onboarding/" + key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return onboarding.(map[string]any), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserGroupsResult struct {
|
||||||
|
Groups []groupsstorage.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetUserGroups(idtoken *oidc.IDToken) (*UserGroupsResult, error) {
|
||||||
|
var claims map[string]any
|
||||||
|
err := idtoken.Claims(&claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := claims["groups"]
|
||||||
|
groups_interface, ok := g.([]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid groups format")
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := []string{}
|
||||||
|
for _, v := range groups_interface {
|
||||||
|
groups = append(groups, v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &grpcapi.GetGroupsBatchRequest{
|
||||||
|
Groupids: groups,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupsresponse []groupsstorage.Group
|
||||||
|
for _, group := range resp.Groups {
|
||||||
|
if group.Namespace != "parcoursmob_organizations" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
g := group.ToStorageType()
|
||||||
|
groupsresponse = append(groupsresponse, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserGroupsResult{Groups: groupsresponse}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,927 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
|
profilepictures "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/profile-pictures"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||||
|
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||||
|
"git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
||||||
|
solidaritytransformers "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
||||||
|
solidaritytypes "git.coopgo.io/coopgo-platform/solidarity-transport/types"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BeneficiariesResult struct {
|
||||||
|
Accounts []mobilityaccountsstorage.Account
|
||||||
|
CacheID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBeneficiaries(ctx context.Context, searchFilter string, archivedFilter bool) (*BeneficiariesResult, error) {
|
||||||
|
accounts, err := h.getBeneficiariesWithFilters(ctx, searchFilter, archivedFilter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.BeneficiariesByName(accounts))
|
||||||
|
|
||||||
|
cacheID := uuid.NewString()
|
||||||
|
h.cache.PutWithTTL(cacheID, accounts, 1*time.Hour)
|
||||||
|
|
||||||
|
return &BeneficiariesResult{
|
||||||
|
Accounts: accounts,
|
||||||
|
CacheID: cacheID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) CreateBeneficiary(ctx context.Context, firstName, lastName, email string, birthdate *time.Time, phoneNumber, fileNumber string, address any, gender string, otherProperties any) (string, error) {
|
||||||
|
// Get current group
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return "", fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
// Create data map for the beneficiary
|
||||||
|
dataMap := map[string]any{
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
"email": email,
|
||||||
|
"phone_number": phoneNumber,
|
||||||
|
"file_number": fileNumber,
|
||||||
|
"gender": gender,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert birthdate to string format for structpb compatibility
|
||||||
|
if birthdate != nil {
|
||||||
|
dataMap["birthdate"] = birthdate.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
if address != nil {
|
||||||
|
dataMap["address"] = address
|
||||||
|
}
|
||||||
|
if otherProperties != nil {
|
||||||
|
dataMap["other_properties"] = otherProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &mobilityaccounts.RegisterRequest{
|
||||||
|
Account: &mobilityaccounts.Account{
|
||||||
|
Namespace: "parcoursmob_beneficiaries",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.Register(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe := &groupsmanagement.SubscribeRequest{
|
||||||
|
Groupid: group.ID,
|
||||||
|
Memberid: resp.Account.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.GroupsManagement.Subscribe(ctx, subscribe)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Account.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BeneficiaryDataResult struct {
|
||||||
|
Account mobilityaccountsstorage.Account
|
||||||
|
Bookings []fleetsstorage.Booking
|
||||||
|
Organizations []any
|
||||||
|
Documents []filestorage.FileInfo
|
||||||
|
EventsList []Event_Beneficiary
|
||||||
|
SolidarityTransportStats map[string]int64
|
||||||
|
SolidarityTransportBookings []*solidaritytypes.Booking
|
||||||
|
SolidarityDriversMap map[string]mobilityaccountsstorage.Account
|
||||||
|
OrganizedCarpoolStats map[string]int64
|
||||||
|
OrganizedCarpoolBookings []*proto.CarpoolServiceBooking
|
||||||
|
OrganizedCarpoolDriversMap map[string]mobilityaccountsstorage.Account
|
||||||
|
WalletBalance float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBeneficiaryData(ctx context.Context, beneficiaryID string) (*BeneficiaryDataResult, error) {
|
||||||
|
// Get beneficiary account
|
||||||
|
request := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security check: ensure this is actually a beneficiary account
|
||||||
|
if resp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return nil, fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, resp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
account := resp.Account.ToStorageType()
|
||||||
|
|
||||||
|
// Get documents
|
||||||
|
documents := h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiaryID)
|
||||||
|
|
||||||
|
// Get events subscriptions
|
||||||
|
subscriptionRequest := &agenda.GetSubscriptionByUserRequest{
|
||||||
|
Subscriber: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionResp, err := h.services.GRPC.Agenda.GetSubscriptionByUser(ctx, subscriptionRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events := []agendastorage.Event{}
|
||||||
|
currentTime := time.Now().Truncate(24 * time.Hour)
|
||||||
|
|
||||||
|
for _, e := range subscriptionResp.Subscription {
|
||||||
|
eventRequest := &agenda.GetEventRequest{
|
||||||
|
Id: e.Eventid,
|
||||||
|
}
|
||||||
|
eventResp, err := h.services.GRPC.Agenda.GetEvent(ctx, eventRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
events = append(events, eventResp.Event.ToStorageType())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.EventsByStartdate(events))
|
||||||
|
|
||||||
|
// Get bookings
|
||||||
|
bookingsRequest := &fleets.GetDriverBookingsRequest{
|
||||||
|
Driver: beneficiaryID,
|
||||||
|
}
|
||||||
|
bookingsResp, err := h.services.GRPC.Fleets.GetDriverBookings(ctx, bookingsRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bookings := []fleetsstorage.Booking{}
|
||||||
|
for _, b := range bookingsResp.Bookings {
|
||||||
|
bookings = append(bookings, b.ToStorageType())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build events list
|
||||||
|
var eventsList []Event_Beneficiary
|
||||||
|
var statusEvent int
|
||||||
|
|
||||||
|
for _, e := range events {
|
||||||
|
if e.Startdate.After(currentTime) {
|
||||||
|
statusEvent = 1
|
||||||
|
} else if e.Startdate.Before(currentTime) && e.Enddate.After(currentTime) || e.Enddate.Equal(currentTime) {
|
||||||
|
statusEvent = 2
|
||||||
|
} else {
|
||||||
|
statusEvent = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
event := Event{
|
||||||
|
NameVal: e.Name,
|
||||||
|
DateVal: e.Startdate,
|
||||||
|
DateEndVal: e.Enddate,
|
||||||
|
TypeVal: e.Type,
|
||||||
|
IDVal: e.ID,
|
||||||
|
DbVal: "/app/agenda/",
|
||||||
|
IconSet: "calendar",
|
||||||
|
StatusVal: statusEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsList = append(eventsList, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add vehicle bookings to events list
|
||||||
|
var statusBooking int
|
||||||
|
for _, b := range bookings {
|
||||||
|
if b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) {
|
||||||
|
getVehicleRequest := &fleets.GetVehicleRequest{
|
||||||
|
Vehicleid: b.Vehicleid,
|
||||||
|
}
|
||||||
|
|
||||||
|
getVehicleResp, err := h.services.GRPC.Fleets.GetVehicle(ctx, getVehicleRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Startdate.After(currentTime) {
|
||||||
|
statusBooking = 1
|
||||||
|
} else if b.Startdate.Before(currentTime) && b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) {
|
||||||
|
statusBooking = 2
|
||||||
|
} else {
|
||||||
|
statusBooking = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
event := Event{
|
||||||
|
NameVal: getVehicleResp.Vehicle.ToStorageType().Data["name"].(string),
|
||||||
|
DateVal: b.Startdate,
|
||||||
|
DateEndVal: b.Enddate,
|
||||||
|
TypeVal: "Réservation de véhicule",
|
||||||
|
IDVal: b.ID,
|
||||||
|
DbVal: "/app/vehicles-management/bookings/",
|
||||||
|
IconSet: "vehicle",
|
||||||
|
StatusVal: statusBooking,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsList = append(eventsList, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get solidarity transport bookings (all statuses for display)
|
||||||
|
solidarityResp, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBookings(ctx, &gen.GetSolidarityTransportBookingsRequest{
|
||||||
|
Passengerid: beneficiaryID,
|
||||||
|
StartDate: timestamppb.New(time.Now().Add(-365 * 24 * time.Hour)),
|
||||||
|
EndDate: timestamppb.New(time.Now().Add(365 * 24 * time.Hour)),
|
||||||
|
})
|
||||||
|
|
||||||
|
protoBookings := []*gen.SolidarityTransportBooking{}
|
||||||
|
if err == nil {
|
||||||
|
protoBookings = solidarityResp.Bookings
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Msg("error retrieving solidarity transport bookings for beneficiary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert proto bookings to types with geojson.Feature
|
||||||
|
solidarityTransportBookings := []*solidaritytypes.Booking{}
|
||||||
|
for _, protoBooking := range protoBookings {
|
||||||
|
booking, err := solidaritytransformers.BookingProtoToType(protoBooking)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error converting booking proto to type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
solidarityTransportBookings = append(solidarityTransportBookings, booking)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect unique driver IDs
|
||||||
|
driverIDs := []string{}
|
||||||
|
driverIDsMap := make(map[string]bool)
|
||||||
|
for _, booking := range solidarityTransportBookings {
|
||||||
|
if booking.DriverId != "" {
|
||||||
|
if !driverIDsMap[booking.DriverId] {
|
||||||
|
driverIDs = append(driverIDs, booking.DriverId)
|
||||||
|
driverIDsMap[booking.DriverId] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get drivers in batch
|
||||||
|
driversMap := make(map[string]mobilityaccountsstorage.Account)
|
||||||
|
if len(driverIDs) > 0 {
|
||||||
|
driversResp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, &mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: driverIDs,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
for _, account := range driversResp.Accounts {
|
||||||
|
a := account.ToStorageType()
|
||||||
|
driversMap[a.ID] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate stats only for validated bookings
|
||||||
|
solidarityTransportStats := map[string]int64{
|
||||||
|
"count": 0,
|
||||||
|
"km": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range solidarityTransportBookings {
|
||||||
|
if b.Status == "VALIDATED" {
|
||||||
|
solidarityTransportStats["count"] = solidarityTransportStats["count"] + 1
|
||||||
|
if b.Journey != nil {
|
||||||
|
solidarityTransportStats["km"] = solidarityTransportStats["km"] + b.Journey.PassengerDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to events list
|
||||||
|
event := Event{
|
||||||
|
NameVal: fmt.Sprintf("%s (%d km)", b.Journey.PassengerDrop.Properties.MustString("label", ""), b.Journey.PassengerDistance),
|
||||||
|
DateVal: b.Journey.PassengerPickupDate,
|
||||||
|
DateEndVal: b.Journey.PassengerPickupDate,
|
||||||
|
TypeVal: "Transport solidaire",
|
||||||
|
IDVal: b.Id,
|
||||||
|
DbVal: "/app/solidarity-transport/bookings/",
|
||||||
|
IconSet: "vehicle",
|
||||||
|
StatusVal: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsList = append(eventsList, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get organized carpool bookings
|
||||||
|
carpoolBookingsResp, err := h.services.GRPC.CarpoolService.GetUserBookings(ctx, &proto.GetUserBookingsRequest{
|
||||||
|
UserId: beneficiaryID,
|
||||||
|
})
|
||||||
|
|
||||||
|
organizedCarpoolBookings := []*proto.CarpoolServiceBooking{}
|
||||||
|
if err == nil {
|
||||||
|
organizedCarpoolBookings = carpoolBookingsResp.Bookings
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Msg("error retrieving organized carpool bookings for beneficiary")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect unique driver IDs from organized carpool bookings
|
||||||
|
carpoolDriverIDs := []string{}
|
||||||
|
carpoolDriverIDsMap := make(map[string]bool)
|
||||||
|
for _, booking := range organizedCarpoolBookings {
|
||||||
|
if booking.Driver != nil && booking.Driver.Id != "" {
|
||||||
|
if !carpoolDriverIDsMap[booking.Driver.Id] {
|
||||||
|
carpoolDriverIDs = append(carpoolDriverIDs, booking.Driver.Id)
|
||||||
|
carpoolDriverIDsMap[booking.Driver.Id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get organized carpool drivers in batch
|
||||||
|
organizedCarpoolDriversMap := make(map[string]mobilityaccountsstorage.Account)
|
||||||
|
if len(carpoolDriverIDs) > 0 {
|
||||||
|
carpoolDriversResp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, &mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: carpoolDriverIDs,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
for _, account := range carpoolDriversResp.Accounts {
|
||||||
|
a := account.ToStorageType()
|
||||||
|
organizedCarpoolDriversMap[a.ID] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate organized carpool stats (only confirmed bookings)
|
||||||
|
organizedCarpoolStats := map[string]int64{
|
||||||
|
"count": 0,
|
||||||
|
"km": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cb := range organizedCarpoolBookings {
|
||||||
|
if cb.Status == proto.CarpoolServiceBookingStatus_CONFIRMED {
|
||||||
|
organizedCarpoolStats["count"]++
|
||||||
|
if cb.Distance != nil {
|
||||||
|
organizedCarpoolStats["km"] += *cb.Distance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build journey name from drop address and distance for events
|
||||||
|
journeyName := "Covoiturage"
|
||||||
|
if cb.PassengerDropAddress != nil {
|
||||||
|
if cb.Distance != nil {
|
||||||
|
journeyName = fmt.Sprintf("%s (%d km)", *cb.PassengerDropAddress, *cb.Distance)
|
||||||
|
} else {
|
||||||
|
journeyName = *cb.PassengerDropAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get departure date
|
||||||
|
departureDate := time.Now()
|
||||||
|
if cb.PassengerPickupDate != nil {
|
||||||
|
departureDate = cb.PassengerPickupDate.AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
event := Event{
|
||||||
|
NameVal: journeyName,
|
||||||
|
DateVal: departureDate,
|
||||||
|
DateEndVal: departureDate,
|
||||||
|
TypeVal: "Covoiturage solidaire",
|
||||||
|
IDVal: cb.Id,
|
||||||
|
DbVal: "/app/organized-carpool/bookings/",
|
||||||
|
IconSet: "vehicle",
|
||||||
|
StatusVal: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
eventsList = append(eventsList, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByDate(eventsList)
|
||||||
|
|
||||||
|
// Get organizations
|
||||||
|
groupsRequest := &groupsmanagement.GetGroupsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_organizations"},
|
||||||
|
Member: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsResp, err := h.services.GRPC.GroupsManagement.GetGroups(ctx, groupsRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
organizations := []any{}
|
||||||
|
for _, o := range groupsResp.Groups {
|
||||||
|
organizations = append(organizations, o.ToStorageType())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate wallet balance
|
||||||
|
walletBalance := h.calculateWalletBalance(account)
|
||||||
|
|
||||||
|
return &BeneficiaryDataResult{
|
||||||
|
Account: account,
|
||||||
|
Bookings: bookings,
|
||||||
|
Organizations: organizations,
|
||||||
|
Documents: documents,
|
||||||
|
EventsList: eventsList,
|
||||||
|
SolidarityTransportStats: solidarityTransportStats,
|
||||||
|
SolidarityTransportBookings: solidarityTransportBookings,
|
||||||
|
SolidarityDriversMap: driversMap,
|
||||||
|
OrganizedCarpoolStats: organizedCarpoolStats,
|
||||||
|
OrganizedCarpoolBookings: organizedCarpoolBookings,
|
||||||
|
OrganizedCarpoolDriversMap: organizedCarpoolDriversMap,
|
||||||
|
WalletBalance: walletBalance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BeneficiaryResult struct {
|
||||||
|
Account mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBeneficiary(ctx context.Context, beneficiaryID string) (*BeneficiaryResult, error) {
|
||||||
|
request := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security check: ensure this is actually a beneficiary account
|
||||||
|
if resp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return nil, fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, resp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BeneficiaryResult{
|
||||||
|
Account: resp.Account.ToStorageType(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UpdateBeneficiary(ctx context.Context, beneficiaryID, firstName, lastName, email string, birthdate *time.Time, phoneNumber, fileNumber string, address any, gender string, otherProperties any) (string, error) {
|
||||||
|
// Security check: verify the account exists and is a beneficiary
|
||||||
|
getRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if getResp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return "", fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create data map for the beneficiary
|
||||||
|
dataMap := map[string]any{
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
"email": email,
|
||||||
|
"phone_number": phoneNumber,
|
||||||
|
"file_number": fileNumber,
|
||||||
|
"gender": gender,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle birthdate conversion for protobuf compatibility
|
||||||
|
if birthdate != nil {
|
||||||
|
dataMap["birthdate"] = birthdate.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
if address != nil {
|
||||||
|
dataMap["address"] = address
|
||||||
|
}
|
||||||
|
if otherProperties != nil {
|
||||||
|
dataMap["other_properties"] = otherProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &mobilityaccounts.UpdateDataRequest{
|
||||||
|
Account: &mobilityaccounts.Account{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
Namespace: "parcoursmob_beneficiaries",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Account.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ArchiveBeneficiary(ctx context.Context, beneficiaryID string) error {
|
||||||
|
// Security check: verify the account exists and is a beneficiary
|
||||||
|
getRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if getResp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(map[string]any{
|
||||||
|
"archived": true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &mobilityaccounts.UpdateDataRequest{
|
||||||
|
Account: &mobilityaccounts.Account{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
Namespace: "parcoursmob_beneficiaries",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UnarchiveBeneficiary(ctx context.Context, beneficiaryID string) error {
|
||||||
|
// Security check: verify the account exists and is a beneficiary
|
||||||
|
getRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if getResp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(map[string]any{
|
||||||
|
"archived": false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &mobilityaccounts.UpdateDataRequest{
|
||||||
|
Account: &mobilityaccounts.Account{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
Namespace: "parcoursmob_beneficiaries",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBeneficiaryPicture(ctx context.Context, beneficiaryID string) ([]byte, string, error) {
|
||||||
|
request := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security check: ensure this is actually a beneficiary account
|
||||||
|
// if resp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
// return nil, "", fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, resp.Account.Namespace)
|
||||||
|
// }
|
||||||
|
|
||||||
|
account := resp.Account.ToStorageType()
|
||||||
|
|
||||||
|
firstName, ok := account.Data["first_name"].(string)
|
||||||
|
if !ok || firstName == "" {
|
||||||
|
firstName = "U"
|
||||||
|
}
|
||||||
|
lastName, ok := account.Data["last_name"].(string)
|
||||||
|
if !ok || lastName == "" {
|
||||||
|
lastName = "U"
|
||||||
|
}
|
||||||
|
|
||||||
|
initials := strings.ToUpper(string(firstName[0]) + string(lastName[0]))
|
||||||
|
picture := profilepictures.DefaultProfilePicture(initials)
|
||||||
|
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
if err := png.Encode(buffer, picture); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.Bytes(), "image/png", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) AddBeneficiaryDocument(ctx context.Context, beneficiaryID string, file io.Reader, filename string, fileSize int64, documentType, documentName string) error {
|
||||||
|
// Security check: verify the account exists and is a beneficiary
|
||||||
|
getRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if getResp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileid := uuid.NewString()
|
||||||
|
|
||||||
|
metadata := map[string]string{
|
||||||
|
"type": documentType,
|
||||||
|
"name": documentName,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.filestorage.Put(file, filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s_%s", beneficiaryID, fileid, filename), fileSize, metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBeneficiaryDocument(ctx context.Context, beneficiaryID, document string) (io.Reader, *filestorage.FileInfo, error) {
|
||||||
|
// Security check: verify the account exists and is a beneficiary
|
||||||
|
getRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if getResp.Account.Namespace != "parcoursmob_beneficiaries" {
|
||||||
|
return nil, nil, fmt.Errorf("account %s is not a beneficiary (namespace: %s)", beneficiaryID, getResp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, info, err := h.filestorage.Get(filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s", beneficiaryID, document))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) DeleteBeneficiaryDocument(ctx context.Context, beneficiaryID, document string) error {
|
||||||
|
return h.DeleteDocument(ctx, BeneficiaryDocumentConfig, beneficiaryID, document)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) getBeneficiariesWithFilters(ctx context.Context, searchFilter string, archivedFilter bool) ([]mobilityaccountsstorage.Account, error) {
|
||||||
|
accounts := []mobilityaccountsstorage.Account{}
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return accounts, errors.New("no group provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
request := &mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: group.Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("issue in mobilityaccounts call")
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, account := range resp.Accounts {
|
||||||
|
if h.filterAccount(account, searchFilter, archivedFilter) {
|
||||||
|
a := account.ToStorageType()
|
||||||
|
accounts = append(accounts, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) filterAccount(a *mobilityaccounts.Account, searchFilter string, archivedFilter bool) bool {
|
||||||
|
// Search filter
|
||||||
|
if searchFilter != "" {
|
||||||
|
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
|
||||||
|
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Archived filter
|
||||||
|
if archivedFilter {
|
||||||
|
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event_Beneficiary interface {
|
||||||
|
Name() string
|
||||||
|
Date() time.Time
|
||||||
|
DateEnd() time.Time
|
||||||
|
Type() string
|
||||||
|
Db() string
|
||||||
|
ID() string
|
||||||
|
Icons() string
|
||||||
|
Status() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
IDVal string
|
||||||
|
NameVal string
|
||||||
|
DateVal time.Time
|
||||||
|
DateEndVal time.Time
|
||||||
|
TypeVal string
|
||||||
|
DbVal string
|
||||||
|
Deleted bool
|
||||||
|
IconSet string
|
||||||
|
StatusVal int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Name() string {
|
||||||
|
return e.NameVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Date() time.Time {
|
||||||
|
return e.DateVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) DateEnd() time.Time {
|
||||||
|
return e.DateEndVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Type() string {
|
||||||
|
return e.TypeVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) ID() string {
|
||||||
|
return e.IDVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Db() string {
|
||||||
|
return e.DbVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Icons() string {
|
||||||
|
return e.IconSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) Status() int {
|
||||||
|
return e.StatusVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortByDate(events []Event_Beneficiary) {
|
||||||
|
sort.Slice(events, func(i, j int) bool {
|
||||||
|
return events[i].Date().After(events[j].Date())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions needed by other modules
|
||||||
|
func filterAccount(r *http.Request, a *mobilityaccounts.Account) bool {
|
||||||
|
searchFilter, ok := r.URL.Query()["search"]
|
||||||
|
|
||||||
|
if ok && len(searchFilter[0]) > 0 {
|
||||||
|
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
|
||||||
|
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter[0])) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
archivedFilter, ok := r.URL.Query()["archived"]
|
||||||
|
if ok && archivedFilter[0] == "true" {
|
||||||
|
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
|
||||||
|
accounts := []mobilityaccountsstorage.Account{}
|
||||||
|
g := r.Context().Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return accounts, errors.New("no group provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
request := &mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: group.Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("issue in mobilityaccounts call")
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, account := range resp.Accounts {
|
||||||
|
if filterAccount(r, account) {
|
||||||
|
a := account.ToStorageType()
|
||||||
|
accounts = append(accounts, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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) (map[string]any, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := json.Marshal(formData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataMap map[string]any
|
||||||
|
err = json.Unmarshal(d, &dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataMap, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||||
|
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
fleetstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/paulmach/orb"
|
||||||
|
"github.com/paulmach/orb/geojson"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DashboardResult struct {
|
||||||
|
Accounts []mobilityaccountsstorage.Account
|
||||||
|
Members []mobilityaccountsstorage.Account
|
||||||
|
Events []agendastorage.Event
|
||||||
|
Bookings []fleetstorage.Booking
|
||||||
|
SolidarityDrivers []mobilityaccountsstorage.Account
|
||||||
|
OrganizedCarpoolDrivers []mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddressGeoLayer, driverAddressGeoCode string) (*DashboardResult, error) {
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return nil, fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
// Load geography polygons for driver address filtering
|
||||||
|
var driverAddressPolygons []orb.Polygon
|
||||||
|
if driverAddressGeoLayer != "" && driverAddressGeoCode != "" {
|
||||||
|
polygons, err := h.loadGeographyPolygon(driverAddressGeoLayer, driverAddressGeoCode)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("failed to load driver address geography filter")
|
||||||
|
} else {
|
||||||
|
driverAddressPolygons = polygons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get accounts (recent beneficiaries)
|
||||||
|
request := &mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: group.Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts := []mobilityaccountsstorage.Account{}
|
||||||
|
|
||||||
|
// We only display the 5 most recent here
|
||||||
|
count := len(resp.Accounts)
|
||||||
|
min := count - 5
|
||||||
|
if min < 0 {
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, account := range resp.Accounts[min:] {
|
||||||
|
// Check if not archived
|
||||||
|
if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived {
|
||||||
|
a := account.ToStorageType()
|
||||||
|
accounts = append([]mobilityaccountsstorage.Account{a}, accounts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch remaining data in parallel using goroutines
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
var members []mobilityaccountsstorage.Account
|
||||||
|
var events []agendastorage.Event
|
||||||
|
var bookings []fleetstorage.Booking
|
||||||
|
var solidarityDrivers []mobilityaccountsstorage.Account
|
||||||
|
var organizedCarpoolDrivers []mobilityaccountsstorage.Account
|
||||||
|
|
||||||
|
// Get members
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
m, _, err := h.groupmembers(group.ID)
|
||||||
|
if err == nil {
|
||||||
|
mu.Lock()
|
||||||
|
members = m
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get events
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
eventsresp, err := h.services.GRPC.Agenda.GetEvents(ctx, &agenda.GetEventsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_dispositifs"},
|
||||||
|
Mindate: timestamppb.Now(),
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
mu.Lock()
|
||||||
|
for _, e := range eventsresp.Events {
|
||||||
|
events = append(events, e.ToStorageType())
|
||||||
|
}
|
||||||
|
sort.Sort(sorting.EventsByStartdate(events))
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get bookings
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
bookingsresp, err := h.services.GRPC.Fleets.GetBookings(ctx, &fleets.GetBookingsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_dispositifs"},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
mu.Lock()
|
||||||
|
for _, b := range bookingsresp.Bookings {
|
||||||
|
if b.Enddate.AsTime().After(time.Now()) {
|
||||||
|
bookings = append(bookings, b.ToStorageType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get solidarity transport drivers
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
solidarityRequest := &mobilityaccounts.GetAccountsRequest{
|
||||||
|
Namespaces: []string{"solidarity_drivers"},
|
||||||
|
}
|
||||||
|
solidarityResp, err := h.services.GRPC.MobilityAccounts.GetAccounts(ctx, solidarityRequest)
|
||||||
|
if err == nil {
|
||||||
|
mu.Lock()
|
||||||
|
for _, account := range solidarityResp.Accounts {
|
||||||
|
// Only include non-archived drivers with addresses
|
||||||
|
if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived {
|
||||||
|
if address, ok := account.Data.AsMap()["address"]; ok && address != nil {
|
||||||
|
// Apply geography filter if specified
|
||||||
|
if len(driverAddressPolygons) > 0 {
|
||||||
|
if addr, ok := account.Data.AsMap()["address"].(map[string]interface{}); ok {
|
||||||
|
jsonAddr, err := json.Marshal(addr)
|
||||||
|
if err == nil {
|
||||||
|
addrGeojson, err := geojson.UnmarshalFeature(jsonAddr)
|
||||||
|
if err == nil && addrGeojson.Geometry != nil {
|
||||||
|
if point, ok := addrGeojson.Geometry.(orb.Point); ok {
|
||||||
|
if isPointInGeographies(point, driverAddressPolygons) {
|
||||||
|
solidarityDrivers = append(solidarityDrivers, account.ToStorageType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
solidarityDrivers = append(solidarityDrivers, account.ToStorageType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get organized carpool drivers
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
carpoolRequest := &mobilityaccounts.GetAccountsRequest{
|
||||||
|
Namespaces: []string{"organized_carpool_drivers"},
|
||||||
|
}
|
||||||
|
carpoolResp, err := h.services.GRPC.MobilityAccounts.GetAccounts(ctx, carpoolRequest)
|
||||||
|
if err == nil {
|
||||||
|
mu.Lock()
|
||||||
|
for _, account := range carpoolResp.Accounts {
|
||||||
|
// Only include non-archived drivers with addresses
|
||||||
|
if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived {
|
||||||
|
if address, ok := account.Data.AsMap()["address"]; ok && address != nil {
|
||||||
|
// Apply geography filter if specified
|
||||||
|
if len(driverAddressPolygons) > 0 {
|
||||||
|
if addr, ok := account.Data.AsMap()["address"].(map[string]interface{}); ok {
|
||||||
|
jsonAddr, err := json.Marshal(addr)
|
||||||
|
if err == nil {
|
||||||
|
addrGeojson, err := geojson.UnmarshalFeature(jsonAddr)
|
||||||
|
if err == nil && addrGeojson.Geometry != nil {
|
||||||
|
if point, ok := addrGeojson.Geometry.(orb.Point); ok {
|
||||||
|
if isPointInGeographies(point, driverAddressPolygons) {
|
||||||
|
organizedCarpoolDrivers = append(organizedCarpoolDrivers, account.ToStorageType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
organizedCarpoolDrivers = append(organizedCarpoolDrivers, account.ToStorageType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for all goroutines to complete
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return &DashboardResult{
|
||||||
|
Accounts: accounts,
|
||||||
|
Members: members,
|
||||||
|
Events: events,
|
||||||
|
Bookings: bookings,
|
||||||
|
SolidarityDrivers: solidarityDrivers,
|
||||||
|
OrganizedCarpoolDrivers: organizedCarpoolDrivers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
// Directory module - no business logic needed, all functionality moved to WebServer handlers
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DocumentConfig defines entity-specific document configuration
|
||||||
|
type DocumentConfig struct {
|
||||||
|
// Storage prefix for this entity type
|
||||||
|
StoragePrefix string
|
||||||
|
|
||||||
|
// Namespace for account validation (empty if no validation needed)
|
||||||
|
AccountNamespace string
|
||||||
|
|
||||||
|
// Whether to validate against MobilityAccounts service
|
||||||
|
RequiresAccountValidation bool
|
||||||
|
|
||||||
|
// Custom validator function (optional)
|
||||||
|
CustomValidator func(ctx context.Context, entityID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-configured document configs for each entity type
|
||||||
|
var (
|
||||||
|
BeneficiaryDocumentConfig = DocumentConfig{
|
||||||
|
StoragePrefix: filestorage.PREFIX_BENEFICIARIES,
|
||||||
|
AccountNamespace: "parcoursmob_beneficiaries",
|
||||||
|
RequiresAccountValidation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
SolidarityDriverDocumentConfig = DocumentConfig{
|
||||||
|
StoragePrefix: filestorage.PREFIX_SOLIDARITY_TRANSPORT_DRIVERS,
|
||||||
|
AccountNamespace: "solidarity_drivers",
|
||||||
|
RequiresAccountValidation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
OrganizedCarpoolDriverDocumentConfig = DocumentConfig{
|
||||||
|
StoragePrefix: filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS,
|
||||||
|
AccountNamespace: "organized_carpool_drivers",
|
||||||
|
RequiresAccountValidation: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
BookingDocumentConfig = DocumentConfig{
|
||||||
|
StoragePrefix: filestorage.PREFIX_BOOKINGS,
|
||||||
|
RequiresAccountValidation: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDocument adds a document for any entity with validation
|
||||||
|
func (h *ApplicationHandler) AddDocument(
|
||||||
|
ctx context.Context,
|
||||||
|
config DocumentConfig,
|
||||||
|
entityID string,
|
||||||
|
file io.Reader,
|
||||||
|
filename string,
|
||||||
|
fileSize int64,
|
||||||
|
documentType string,
|
||||||
|
documentName string,
|
||||||
|
) error {
|
||||||
|
// Perform validation if required
|
||||||
|
if config.RequiresAccountValidation {
|
||||||
|
if err := h.validateAccountForDocument(ctx, entityID, config.AccountNamespace); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validation if provided
|
||||||
|
if config.CustomValidator != nil {
|
||||||
|
if err := config.CustomValidator(ctx, entityID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique file ID
|
||||||
|
fileid := uuid.NewString()
|
||||||
|
|
||||||
|
// Prepare metadata
|
||||||
|
metadata := map[string]string{
|
||||||
|
"type": documentType,
|
||||||
|
"name": documentName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct file path
|
||||||
|
filepath := fmt.Sprintf("%s/%s_%s", entityID, fileid, filename)
|
||||||
|
|
||||||
|
// Store file
|
||||||
|
return h.filestorage.Put(file, config.StoragePrefix, filepath, fileSize, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDocument retrieves a document for any entity with validation
|
||||||
|
func (h *ApplicationHandler) GetDocument(
|
||||||
|
ctx context.Context,
|
||||||
|
config DocumentConfig,
|
||||||
|
entityID string,
|
||||||
|
document string,
|
||||||
|
) (io.Reader, *filestorage.FileInfo, error) {
|
||||||
|
// Perform validation if required
|
||||||
|
if config.RequiresAccountValidation {
|
||||||
|
if err := h.validateAccountForDocument(ctx, entityID, config.AccountNamespace); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validation if provided
|
||||||
|
if config.CustomValidator != nil {
|
||||||
|
if err := config.CustomValidator(ctx, entityID); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve file
|
||||||
|
filepath := fmt.Sprintf("%s/%s", entityID, document)
|
||||||
|
return h.filestorage.Get(config.StoragePrefix, filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDocuments retrieves all documents for an entity
|
||||||
|
func (h *ApplicationHandler) ListDocuments(
|
||||||
|
config DocumentConfig,
|
||||||
|
entityID string,
|
||||||
|
) []filestorage.FileInfo {
|
||||||
|
prefix := fmt.Sprintf("%s/%s", config.StoragePrefix, entityID)
|
||||||
|
return h.filestorage.List(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDocument deletes a document for any entity with validation
|
||||||
|
func (h *ApplicationHandler) DeleteDocument(
|
||||||
|
ctx context.Context,
|
||||||
|
config DocumentConfig,
|
||||||
|
entityID string,
|
||||||
|
document string,
|
||||||
|
) error {
|
||||||
|
// Perform validation if required
|
||||||
|
if config.RequiresAccountValidation {
|
||||||
|
if err := h.validateAccountForDocument(ctx, entityID, config.AccountNamespace); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validation if provided
|
||||||
|
if config.CustomValidator != nil {
|
||||||
|
if err := config.CustomValidator(ctx, entityID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file
|
||||||
|
filepath := fmt.Sprintf("%s/%s", entityID, document)
|
||||||
|
return h.filestorage.Delete(config.StoragePrefix, filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAccountForDocument validates entity against MobilityAccounts service
|
||||||
|
func (h *ApplicationHandler) validateAccountForDocument(ctx context.Context, accountID string, expectedNamespace string) error {
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: accountID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Account.Namespace != expectedNamespace {
|
||||||
|
return fmt.Errorf("account %s is not of type %s (namespace: %s)",
|
||||||
|
accountID, expectedNamespace, resp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,429 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||||
|
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
accountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlatMaps []map[string]any
|
||||||
|
|
||||||
|
func (maps FlatMaps) GetHeaders() (res []string) {
|
||||||
|
keys := map[string]bool{}
|
||||||
|
for _, m := range maps {
|
||||||
|
for k, _ := range m {
|
||||||
|
if _, ok := keys[k]; !ok {
|
||||||
|
keys[k] = true
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (maps FlatMaps) GetValues() (res [][]string) {
|
||||||
|
headers := maps.GetHeaders()
|
||||||
|
for _, m := range maps {
|
||||||
|
line := []string{}
|
||||||
|
for _, k := range headers {
|
||||||
|
if v, ok := m[k]; ok && v != nil {
|
||||||
|
line = append(line, fmt.Sprint(v))
|
||||||
|
} else {
|
||||||
|
line = append(line, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = append(res, line)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportCacheResult struct {
|
||||||
|
Headers []string
|
||||||
|
Values [][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ExportCacheAsCSV(cacheID string) (*ExportCacheResult, error) {
|
||||||
|
d, err := h.cache.Get(cacheID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []any
|
||||||
|
if dataSlice, ok := d.([]any); ok {
|
||||||
|
data = dataSlice
|
||||||
|
} else {
|
||||||
|
// Convert single item to slice
|
||||||
|
jsonData, err := json.Marshal(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(jsonData, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flatmaps := FlatMaps{}
|
||||||
|
for _, v := range data {
|
||||||
|
if vMap, ok := v.(map[string]any); ok {
|
||||||
|
fm := map[string]any{}
|
||||||
|
flatten("", vMap, fm)
|
||||||
|
flatmaps = append(flatmaps, fm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ExportCacheResult{
|
||||||
|
Headers: flatmaps.GetHeaders(),
|
||||||
|
Values: flatmaps.GetValues(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flatten(prefix string, src map[string]any, dest map[string]any) {
|
||||||
|
if len(prefix) > 0 {
|
||||||
|
prefix += "."
|
||||||
|
}
|
||||||
|
for k, v := range src {
|
||||||
|
switch child := v.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
flatten(prefix+k, child, dest)
|
||||||
|
case []any:
|
||||||
|
for i := 0; i < len(child); i++ {
|
||||||
|
dest[prefix+k+"."+strconv.Itoa(i)] = child[i]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
dest[prefix+k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgendaExportResult struct {
|
||||||
|
ExcelFile *excelize.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ExportAllAgendaEvents() (*AgendaExportResult, error) {
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_dispositifs"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events := []agendastorage.Event{}
|
||||||
|
groupids := []string{}
|
||||||
|
beneficiaries_ids := []string{}
|
||||||
|
|
||||||
|
for _, e := range resp.Events {
|
||||||
|
groupids = append(groupids, e.Owners...)
|
||||||
|
events = append(events, e.ToStorageType())
|
||||||
|
|
||||||
|
for _, subscriptions := range e.Subscriptions {
|
||||||
|
beneficiaries_ids = append(beneficiaries_ids, subscriptions.Subscriber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.EventsByStartdate(events))
|
||||||
|
|
||||||
|
groups, beneficiaries_map, err := h.getAgendaMetadata(groupids, beneficiaries_ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file := h.generateAgendaExcel(events, groups, beneficiaries_map)
|
||||||
|
return &AgendaExportResult{ExcelFile: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ExportSingleAgendaEvent(eventID string) (*AgendaExportResult, error) {
|
||||||
|
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), &agenda.GetEventRequest{
|
||||||
|
Id: eventID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groupids := []string{}
|
||||||
|
beneficiaries_ids := []string{}
|
||||||
|
groupids = append(groupids, resp.Event.Owners...)
|
||||||
|
|
||||||
|
for _, subscriptions := range resp.Event.Subscriptions {
|
||||||
|
beneficiaries_ids = append(beneficiaries_ids, subscriptions.Subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, beneficiaries_map, err := h.getAgendaMetadata(groupids, beneficiaries_ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events := []agendastorage.Event{resp.Event.ToStorageType()}
|
||||||
|
file := h.generateAgendaExcel(events, groups, beneficiaries_map)
|
||||||
|
return &AgendaExportResult{ExcelFile: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) getAgendaMetadata(groupids, beneficiaries_ids []string) (map[string]groupsstorage.Group, map[string]accountsstorage.Account, error) {
|
||||||
|
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: groupids,
|
||||||
|
})
|
||||||
|
|
||||||
|
groups := map[string]groupsstorage.Group{}
|
||||||
|
if err == nil {
|
||||||
|
for _, g := range groupsresp.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: beneficiaries_ids,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries_map := map[string]accountsstorage.Account{}
|
||||||
|
for _, ben := range beneficiaries.Accounts {
|
||||||
|
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, beneficiaries_map, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) generateAgendaExcel(events []agendastorage.Event, groups map[string]groupsstorage.Group, beneficiaries_map map[string]accountsstorage.Account) *excelize.File {
|
||||||
|
f := excelize.NewFile()
|
||||||
|
|
||||||
|
f.SetCellValue("Sheet1", "A1", "Evénement")
|
||||||
|
f.SetCellValue("Sheet1", "B1", "Date de début")
|
||||||
|
f.SetCellValue("Sheet1", "C1", "Date de fin")
|
||||||
|
f.SetCellValue("Sheet1", "D1", "Nom bénéficiaire")
|
||||||
|
f.SetCellValue("Sheet1", "E1", "Prenom bénéficiaire")
|
||||||
|
f.SetCellValue("Sheet1", "F1", "Numéro allocataire / Pole emploi")
|
||||||
|
f.SetCellValue("Sheet1", "G1", "Prescipteur")
|
||||||
|
f.SetCellValue("Sheet1", "H1", "Prescipteur Nom")
|
||||||
|
f.SetCellValue("Sheet1", "I1", "Prescipteur Email")
|
||||||
|
f.SetCellValue("Sheet1", "J1", "Gestionnaire événement")
|
||||||
|
|
||||||
|
i := 2
|
||||||
|
for _, e := range events {
|
||||||
|
if len(e.Owners) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
admin := groups[e.Owners[0]]
|
||||||
|
|
||||||
|
for _, s := range e.Subscriptions {
|
||||||
|
subscribedbygroup := ""
|
||||||
|
subscribedbyuser := ""
|
||||||
|
subscribedbyemail := ""
|
||||||
|
if v, ok := s.Data["subscribed_by"].(map[string]any); ok {
|
||||||
|
if v2, ok := v["group"].(map[string]any); ok {
|
||||||
|
if v3, ok := v2["name"].(string); ok {
|
||||||
|
subscribedbygroup = v3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v4, ok := v["user"].(map[string]any); ok {
|
||||||
|
if v5, ok := v4["display_name"].(string); ok {
|
||||||
|
subscribedbyuser = v5
|
||||||
|
}
|
||||||
|
if v6, ok := v4["email"].(string); ok {
|
||||||
|
subscribedbyemail = v6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiary := beneficiaries_map[s.Subscriber]
|
||||||
|
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i), e.Name)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i), e.Startdate.Format("2006-01-02"))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i), e.Enddate.Format("2006-01-02"))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i), beneficiary.Data["last_name"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i), beneficiary.Data["first_name"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i), beneficiary.Data["file_number"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i), subscribedbygroup)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i), subscribedbyuser)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("I%d", i), subscribedbyemail)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("J%d", i), admin.Data["name"])
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type FleetBookingsExportResult struct {
|
||||||
|
ExcelFile *excelize.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ExportAllFleetBookings() (*FleetBookingsExportResult, error) {
|
||||||
|
vehicles, bookings, err := h.getFleetData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, beneficiaries_map, err := h.getFleetMetadata(bookings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file := h.generateFleetExcel(bookings, vehicles, groups, beneficiaries_map, "")
|
||||||
|
return &FleetBookingsExportResult{ExcelFile: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ExportFleetBookingsByGroup(groupID string) (*FleetBookingsExportResult, error) {
|
||||||
|
vehicles, bookings, err := h.getFleetData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, beneficiaries_map, err := h.getFleetMetadata(bookings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file := h.generateFleetExcel(bookings, vehicles, groups, beneficiaries_map, groupID)
|
||||||
|
return &FleetBookingsExportResult{ExcelFile: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) getFleetData() (map[string]fleetsstorage.Vehicle, []fleetsstorage.Booking, error) {
|
||||||
|
vehicles := map[string]fleetsstorage.Vehicle{}
|
||||||
|
bookings := []fleetsstorage.Booking{}
|
||||||
|
|
||||||
|
request := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vehicle := range resp.Vehicles {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
for _, b := range v.Bookings {
|
||||||
|
bookings = append(bookings, b)
|
||||||
|
}
|
||||||
|
vehicles[vehicle.Id] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return vehicles, bookings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) getFleetMetadata(bookings []fleetsstorage.Booking) (map[string]groupsstorage.Group, map[string]accountsstorage.Account, error) {
|
||||||
|
beneficiaries_ids := []string{}
|
||||||
|
for _, b := range bookings {
|
||||||
|
beneficiaries_ids = append(beneficiaries_ids, b.Driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := map[string]groupsstorage.Group{}
|
||||||
|
admingroups, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), &groupsmanagement.GetGroupsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_organizations"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range admingroups.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: beneficiaries_ids,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries_map := map[string]accountsstorage.Account{}
|
||||||
|
for _, ben := range beneficiaries.Accounts {
|
||||||
|
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, beneficiaries_map, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) generateFleetExcel(bookings []fleetsstorage.Booking, vehicles map[string]fleetsstorage.Vehicle, groups map[string]groupsstorage.Group, beneficiaries_map map[string]accountsstorage.Account, filterGroupID string) *excelize.File {
|
||||||
|
f := excelize.NewFile()
|
||||||
|
|
||||||
|
f.SetCellValue("Sheet1", "A1", "Numéro")
|
||||||
|
f.SetCellValue("Sheet1", "B1", "Type")
|
||||||
|
f.SetCellValue("Sheet1", "C1", "Gestionnaire")
|
||||||
|
f.SetCellValue("Sheet1", "D1", "Prescripteur")
|
||||||
|
f.SetCellValue("Sheet1", "E1", "Bénéficiaire")
|
||||||
|
f.SetCellValue("Sheet1", "F1", "Numéro allocataire / Pole emploi")
|
||||||
|
f.SetCellValue("Sheet1", "G1", "Début de Mise à disposition")
|
||||||
|
f.SetCellValue("Sheet1", "H1", "Fin de mise à disposition")
|
||||||
|
f.SetCellValue("Sheet1", "I1", "Début indisponibilité")
|
||||||
|
f.SetCellValue("Sheet1", "J1", "Fin indisponibilité")
|
||||||
|
f.SetCellValue("Sheet1", "K1", "Véhicule retiré")
|
||||||
|
f.SetCellValue("Sheet1", "L1", "Commentaire - Retrait véhicule")
|
||||||
|
f.SetCellValue("Sheet1", "M1", "Réservation supprimée")
|
||||||
|
f.SetCellValue("Sheet1", "N1", "Motif de la suppression")
|
||||||
|
|
||||||
|
i := 2
|
||||||
|
for _, b := range bookings {
|
||||||
|
vehicle := vehicles[b.Vehicleid]
|
||||||
|
if len(vehicle.Administrators) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
admin := groups[vehicle.Administrators[0]]
|
||||||
|
|
||||||
|
bookedby := ""
|
||||||
|
if v, ok := b.Data["booked_by"].(map[string]any); ok {
|
||||||
|
if v2, ok := v["user"].(map[string]any); ok {
|
||||||
|
if v3, ok := v2["display_name"].(string); ok {
|
||||||
|
bookedby = v3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookedbygroup := ""
|
||||||
|
if v4, ok := b.Data["booked_by"].(map[string]any); ok {
|
||||||
|
if v5, ok := v4["group"].(map[string]any); ok {
|
||||||
|
if v6, ok := v5["id"].(string); ok {
|
||||||
|
bookedbygroup = v6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by group if specified
|
||||||
|
if filterGroupID != "" && bookedbygroup != filterGroupID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiary := beneficiaries_map[b.Driver]
|
||||||
|
adminunavailability := false
|
||||||
|
|
||||||
|
if av, ok := b.Data["administrator_unavailability"].(bool); ok && av {
|
||||||
|
adminunavailability = true
|
||||||
|
}
|
||||||
|
|
||||||
|
deleted := ""
|
||||||
|
if b.Deleted {
|
||||||
|
deleted = "DELETED"
|
||||||
|
}
|
||||||
|
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i), vehicle.Data["licence_plate"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i), vehicle.Type)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i), admin.Data["name"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i), bookedby)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i), fmt.Sprintf("%v %v", beneficiary.Data["first_name"], beneficiary.Data["last_name"]))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i), beneficiary.Data["file_number"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i), b.Startdate.Format("2006-01-02"))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i), b.Enddate.Format("2006-01-02"))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("I%d", i), b.Unavailablefrom.Format("2006-01-02"))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("J%d", i), b.Unavailableto.Format("2006-01-02"))
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("K%d", i), adminunavailability)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("L%d", i), b.Data["comment"])
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("M%d", i), deleted)
|
||||||
|
f.SetCellValue("Sheet1", fmt.Sprintf("N%d", i), b.Data["motif"])
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupSettingsResult struct {
|
||||||
|
Group storage.Group
|
||||||
|
GroupMembers []any
|
||||||
|
Admins []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetGroupSettings(ctx context.Context, groupID string) (*GroupSettingsResult, error) {
|
||||||
|
// Get group info
|
||||||
|
groupResp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: groupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group := groupResp.Group.ToStorageType()
|
||||||
|
|
||||||
|
members, err := h.members()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get members: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
admins := []any{}
|
||||||
|
groupMembers := []any{}
|
||||||
|
|
||||||
|
for _, m := range members {
|
||||||
|
mm := m.ToStorageType()
|
||||||
|
if groups, ok := mm.Data["groups"].([]any); ok {
|
||||||
|
for _, g := range groups {
|
||||||
|
if g.(string) == groupID {
|
||||||
|
groupMembers = append(groupMembers, mm)
|
||||||
|
}
|
||||||
|
if g.(string) == groupID+":admin" {
|
||||||
|
admins = append(admins, mm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GroupSettingsResult{
|
||||||
|
Group: group,
|
||||||
|
GroupMembers: groupMembers,
|
||||||
|
Admins: admins,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) InviteMemberToGroup(ctx context.Context, groupID string, username string) error {
|
||||||
|
// Get group info
|
||||||
|
groupResp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: groupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
group := groupResp.Group.ToStorageType()
|
||||||
|
|
||||||
|
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(ctx, &accounts.GetAccountUsernameRequest{
|
||||||
|
Username: username,
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Account already exists: adding the existing account to group
|
||||||
|
account := accountresp.Account.ToStorageType()
|
||||||
|
if account.Data["groups"] == nil {
|
||||||
|
account.Data["groups"] = []any{}
|
||||||
|
}
|
||||||
|
account.Data["groups"] = append(account.Data["groups"].([]any), groupID)
|
||||||
|
|
||||||
|
as, err := accounts.AccountFromStorageType(&account)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, &accounts.UpdateDataRequest{
|
||||||
|
Account: as,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"group": group.Data["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("onboarding.existing_member", username, data); err != nil {
|
||||||
|
log.Warn().Err(err).Msg("failed to send existing member email")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create onboarding for new member
|
||||||
|
onboarding := map[string]any{
|
||||||
|
"username": username,
|
||||||
|
"group": groupID,
|
||||||
|
"admin": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return fmt.Errorf("failed to generate random key: %w", err)
|
||||||
|
}
|
||||||
|
key := base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
h.cache.PutWithTTL("onboarding/"+key, onboarding, 72*time.Hour)
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"group": group.Data["name"],
|
||||||
|
"key": key,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.emailing.Send("onboarding.new_member", username, data); err != nil {
|
||||||
|
return fmt.Errorf("failed to send new member email: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupsResult struct {
|
||||||
|
Groups []groupstorage.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateGroupModuleResult struct {
|
||||||
|
GroupID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupModuleCreateDataResult struct {
|
||||||
|
GroupTypes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DisplayGroupModuleResult struct {
|
||||||
|
GroupID string
|
||||||
|
Accounts []any
|
||||||
|
CacheID string
|
||||||
|
Searched bool
|
||||||
|
Beneficiary any
|
||||||
|
Group groupstorage.Group
|
||||||
|
AccountsBeneficiaire []mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
var Addres any
|
||||||
|
|
||||||
|
type BeneficiariesGroupForm 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"`
|
||||||
|
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
|
||||||
|
Address any `json:"address,omitempty"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupsModuleByName []groupstorage.Group
|
||||||
|
|
||||||
|
func (a GroupsModuleByName) Len() int { return len(a) }
|
||||||
|
func (a GroupsModuleByName) Less(i, j int) bool {
|
||||||
|
return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
|
||||||
|
}
|
||||||
|
func (a GroupsModuleByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetGroups(ctx context.Context) (*GroupsResult, error) {
|
||||||
|
request := &groupsmanagement.GetGroupsRequest{
|
||||||
|
Namespaces: []string{"parcoursmob_groups"},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.GroupsManagement.GetGroups(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups = []groupstorage.Group{}
|
||||||
|
|
||||||
|
for _, group := range resp.Groups {
|
||||||
|
g := group.ToStorageType()
|
||||||
|
groups = append(groups, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(GroupsModuleByName(groups))
|
||||||
|
|
||||||
|
return &GroupsResult{
|
||||||
|
Groups: groups,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetGroupModuleCreateData(ctx context.Context) (*GroupModuleCreateDataResult, error) {
|
||||||
|
groupTypes := h.config.GetStringSlice("modules.groups.group_types")
|
||||||
|
return &GroupModuleCreateDataResult{
|
||||||
|
GroupTypes: groupTypes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) CreateGroupModule(ctx context.Context, name, groupType, description, address string) (*CreateGroupModuleResult, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, fmt.Errorf("name is required")
|
||||||
|
}
|
||||||
|
if groupType == "" {
|
||||||
|
return nil, fmt.Errorf("type is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressData any
|
||||||
|
if address != "" {
|
||||||
|
if err := json.Unmarshal([]byte(address), &addressData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse address: %w", err)
|
||||||
|
}
|
||||||
|
Addres = addressData
|
||||||
|
}
|
||||||
|
|
||||||
|
groupID := uuid.NewString()
|
||||||
|
|
||||||
|
dataMap := map[string]any{
|
||||||
|
"name": name,
|
||||||
|
"type": groupType,
|
||||||
|
"description": description,
|
||||||
|
"address": Addres,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create data structure: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &groupsmanagement.AddGroupRequest{
|
||||||
|
Group: &groupsmanagement.Group{
|
||||||
|
Id: groupID,
|
||||||
|
Namespace: "parcoursmob_groups",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.GroupsManagement.AddGroup(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CreateGroupModuleResult{
|
||||||
|
GroupID: groupID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterAccountBySearch(searchFilter string, a *mobilityaccounts.Account) bool {
|
||||||
|
if searchFilter != "" {
|
||||||
|
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
|
||||||
|
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (h *ApplicationHandler) DisplayGroupModule(ctx context.Context, groupID string, searchFilter string, currentUserGroup groupstorage.Group) (*DisplayGroupModuleResult, error) {
|
||||||
|
request := &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: groupID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var accounts = []any{}
|
||||||
|
|
||||||
|
accountsRequest := &mobilityaccounts.GetAccountsBatchRequest{
|
||||||
|
Accountids: resp.Group.Members,
|
||||||
|
}
|
||||||
|
|
||||||
|
accountsResp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(ctx, accountsRequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msg("failed to get accounts batch")
|
||||||
|
} else {
|
||||||
|
for _, account := range accountsResp.Accounts {
|
||||||
|
if filterAccountBySearch(searchFilter, account) {
|
||||||
|
a := account.ToStorageType()
|
||||||
|
accounts = append(accounts, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheID := uuid.NewString()
|
||||||
|
h.cache.PutWithTTL(cacheID, accounts, 1*time.Hour)
|
||||||
|
|
||||||
|
// Get beneficiaries in current user's group
|
||||||
|
accountsBeneficiaire, err := h.services.GetBeneficiariesInGroup(currentUserGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get beneficiaries in group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DisplayGroupModuleResult{
|
||||||
|
GroupID: resp.Group.ToStorageType().ID,
|
||||||
|
Accounts: accounts,
|
||||||
|
CacheID: cacheID,
|
||||||
|
Searched: false,
|
||||||
|
Beneficiary: nil,
|
||||||
|
Group: resp.Group.ToStorageType(),
|
||||||
|
AccountsBeneficiaire: accountsBeneficiaire,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) SubscribeBeneficiaryToGroup(ctx context.Context, groupID string, beneficiaryID string) error {
|
||||||
|
beneficiaryRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaryResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, beneficiaryRequest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get beneficiary: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe := &groupsmanagement.SubscribeRequest{
|
||||||
|
Groupid: groupID,
|
||||||
|
Memberid: beneficiaryResp.Account.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.GroupsManagement.Subscribe(ctx, subscribe)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to subscribe beneficiary to group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,401 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
carpoolproto "git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit/transitous"
|
||||||
|
savedsearchtypes "git.coopgo.io/coopgo-platform/saved-search/data/types"
|
||||||
|
savedsearchproto "git.coopgo.io/coopgo-platform/saved-search/servers/grpc/proto/gen"
|
||||||
|
savedsearchtransformers "git.coopgo.io/coopgo-platform/saved-search/servers/grpc/transformers"
|
||||||
|
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
||||||
|
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
||||||
|
"github.com/paulmach/orb/geojson"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchJourneysResult struct {
|
||||||
|
CarpoolResults []*geojson.FeatureCollection
|
||||||
|
TransitResults []*transitous.Itinerary
|
||||||
|
VehicleResults []fleetsstorage.Vehicle
|
||||||
|
Searched bool
|
||||||
|
DriverJourneys []*gen.SolidarityTransportDriverJourney
|
||||||
|
Drivers map[string]mobilityaccountsstorage.Account
|
||||||
|
OrganizedCarpools []*carpoolproto.CarpoolServiceDriverJourney
|
||||||
|
KnowledgeBaseResults []any
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchJourneys performs the business logic for journey search
|
||||||
|
func (h *ApplicationHandler) SearchJourneys(
|
||||||
|
ctx context.Context,
|
||||||
|
departureDateTime time.Time,
|
||||||
|
departureGeo *geojson.Feature,
|
||||||
|
destinationGeo *geojson.Feature,
|
||||||
|
passengerID string,
|
||||||
|
solidarityTransportExcludeDriver string,
|
||||||
|
) (*SearchJourneysResult, error) {
|
||||||
|
var (
|
||||||
|
// Results
|
||||||
|
transitResults []*transitous.Itinerary
|
||||||
|
carpoolResults []*geojson.FeatureCollection
|
||||||
|
vehicleResults []fleetsstorage.Vehicle
|
||||||
|
solidarityTransportResults []*gen.SolidarityTransportDriverJourney
|
||||||
|
organizedCarpoolResults []*carpoolproto.CarpoolServiceDriverJourney
|
||||||
|
knowledgeBaseResults []any
|
||||||
|
|
||||||
|
drivers = map[string]mobilityaccountsstorage.Account{}
|
||||||
|
searched = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// Only search if we have complete departure and destination info
|
||||||
|
if departureGeo != nil && destinationGeo != nil && !departureDateTime.IsZero() {
|
||||||
|
searched = true
|
||||||
|
|
||||||
|
// SOLIDARITY TRANSPORT
|
||||||
|
var err error
|
||||||
|
drivers, err = h.services.GetAccountsInNamespacesMap([]string{"solidarity_drivers", "organized_carpool_drivers"})
|
||||||
|
if err != nil {
|
||||||
|
drivers = map[string]mobilityaccountsstorage.Account{}
|
||||||
|
}
|
||||||
|
|
||||||
|
protodep, _ := transformers.GeoJsonToProto(departureGeo)
|
||||||
|
protodest, _ := transformers.GeoJsonToProto(destinationGeo)
|
||||||
|
|
||||||
|
log.Debug().Time("departure time", departureDateTime).Msg("calling driver journeys with ...")
|
||||||
|
|
||||||
|
res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(ctx, &gen.GetDriverJourneysRequest{
|
||||||
|
Departure: protodep,
|
||||||
|
Arrival: protodest,
|
||||||
|
DepartureDate: timestamppb.New(departureDateTime),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error in grpc call to GetDriverJourneys")
|
||||||
|
} else {
|
||||||
|
solidarityTransportResults = slices.Collect(func(yield func(*gen.SolidarityTransportDriverJourney) bool) {
|
||||||
|
for _, dj := range res.DriverJourneys {
|
||||||
|
if a, ok := drivers[dj.DriverId].Data["archived"]; ok {
|
||||||
|
if archived, ok := a.(bool); ok {
|
||||||
|
if archived {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dj.DriverId == solidarityTransportExcludeDriver {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !yield(dj) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sort.Slice(solidarityTransportResults, func(i, j int) bool {
|
||||||
|
return solidarityTransportResults[i].DriverDistance < solidarityTransportResults[j].DriverDistance
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get departure and destination addresses from properties
|
||||||
|
var departureAddress, destinationAddress string
|
||||||
|
if departureGeo.Properties != nil {
|
||||||
|
if label, ok := departureGeo.Properties["label"].(string); ok {
|
||||||
|
departureAddress = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if destinationGeo.Properties != nil {
|
||||||
|
if label, ok := destinationGeo.Properties["label"].(string); ok {
|
||||||
|
destinationAddress = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
radius := float64(5)
|
||||||
|
// ORGANIZED CARPOOL
|
||||||
|
organizedCarpoolResultsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(ctx, &carpoolproto.DriverJourneysRequest{
|
||||||
|
DepartureLat: departureGeo.Point().Lat(),
|
||||||
|
DepartureLng: departureGeo.Point().Lon(),
|
||||||
|
ArrivalLat: destinationGeo.Point().Lat(),
|
||||||
|
ArrivalLng: destinationGeo.Point().Lon(),
|
||||||
|
DepartureDate: timestamppb.New(departureDateTime),
|
||||||
|
DepartureAddress: &departureAddress,
|
||||||
|
ArrivalAddress: &destinationAddress,
|
||||||
|
DepartureRadius: &radius,
|
||||||
|
ArrivalRadius: &radius,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error retrieving organized carpools")
|
||||||
|
} else {
|
||||||
|
organizedCarpoolResults = organizedCarpoolResultsRes.DriverJourneys
|
||||||
|
sort.Slice(organizedCarpoolResults, func(i, j int) bool {
|
||||||
|
return *organizedCarpoolResults[i].Distance < *organizedCarpoolResults[j].Distance
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
// CARPOOL OPERATORS
|
||||||
|
carpools := make(chan *geojson.FeatureCollection)
|
||||||
|
go h.services.InteropCarpool.Search(carpools, *departureGeo, *destinationGeo, departureDateTime)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for c := range carpools {
|
||||||
|
carpoolResults = append(carpoolResults, c)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TRANSIT
|
||||||
|
transitch := make(chan *transitous.Itinerary)
|
||||||
|
go func(transitch chan *transitous.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
|
||||||
|
defer close(transitch)
|
||||||
|
response, err := h.services.TransitRouting.PlanWithResponse(ctx, &transitous.PlanParams{
|
||||||
|
FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()),
|
||||||
|
ToPlace: fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()),
|
||||||
|
Time: datetime,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error retrieving transit data from Transitous server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, i := range response.Itineraries {
|
||||||
|
transitch <- &i
|
||||||
|
}
|
||||||
|
}(transitch, departureGeo, destinationGeo, &departureDateTime)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
paris, _ := time.LoadLocation("Europe/Paris")
|
||||||
|
requestedDay := departureDateTime.In(paris).Truncate(24 * time.Hour)
|
||||||
|
|
||||||
|
for itinerary := range transitch {
|
||||||
|
// Only include journeys that start on the requested day (in Paris timezone)
|
||||||
|
if !itinerary.StartTime.IsZero() && !itinerary.EndTime.IsZero() {
|
||||||
|
log.Info().
|
||||||
|
Time("startTime", itinerary.StartTime).
|
||||||
|
Time("endTime", itinerary.EndTime).
|
||||||
|
Str("startTimezone", itinerary.StartTime.Location().String()).
|
||||||
|
Str("endTimezone", itinerary.EndTime.Location().String()).
|
||||||
|
Str("startTimeRFC3339", itinerary.StartTime.Format(time.RFC3339)).
|
||||||
|
Str("endTimeRFC3339", itinerary.EndTime.Format(time.RFC3339)).
|
||||||
|
Msg("Journey search - received transit itinerary from Transitous")
|
||||||
|
|
||||||
|
startInParis := itinerary.StartTime.In(paris)
|
||||||
|
startDay := startInParis.Truncate(24 * time.Hour)
|
||||||
|
|
||||||
|
// Check if journey starts on the requested day
|
||||||
|
if startDay.Equal(requestedDay) {
|
||||||
|
transitResults = append(transitResults, itinerary)
|
||||||
|
} else {
|
||||||
|
log.Info().
|
||||||
|
Str("requestedDay", requestedDay.Format("2006-01-02")).
|
||||||
|
Str("startDay", startDay.Format("2006-01-02")).
|
||||||
|
Msg("Journey search - filtered out transit journey (not on requested day)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// VEHICLES
|
||||||
|
vehiclech := make(chan fleetsstorage.Vehicle)
|
||||||
|
go h.vehicleRequest(vehiclech, departureDateTime.Add(-24*time.Hour), departureDateTime.Add(168*time.Hour))
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for vehicle := range vehiclech {
|
||||||
|
vehicleResults = append(vehicleResults, vehicle)
|
||||||
|
}
|
||||||
|
slices.SortFunc(vehicleResults, sorting.VehiclesByDistanceFrom(*departureGeo))
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// KNOWLEDGE BASE
|
||||||
|
departureGeoSearch, _ := h.services.Geography.GeoSearch(departureGeo)
|
||||||
|
kbData := h.config.Get("knowledge_base")
|
||||||
|
if kb, ok := kbData.([]any); ok {
|
||||||
|
for _, sol := range kb {
|
||||||
|
if solution, ok := sol.(map[string]any); ok {
|
||||||
|
if g, ok := solution["geography"]; ok {
|
||||||
|
if geography, ok := g.([]any); ok {
|
||||||
|
for _, gg := range geography {
|
||||||
|
if geog, ok := gg.(map[string]any); ok {
|
||||||
|
if layer, ok := geog["layer"].(string); ok {
|
||||||
|
code := geog["code"]
|
||||||
|
geo, err := h.services.Geography.Find(layer, fmt.Sprintf("%v", code))
|
||||||
|
if err == nil {
|
||||||
|
geog["geography"] = geo
|
||||||
|
geog["name"] = geo.Properties.MustString("nom")
|
||||||
|
}
|
||||||
|
if strings.Compare(fmt.Sprintf("%v", code), departureGeoSearch[layer].Properties.MustString("code")) == 0 {
|
||||||
|
knowledgeBaseResults = append(knowledgeBaseResults, solution)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SearchJourneysResult{
|
||||||
|
CarpoolResults: carpoolResults,
|
||||||
|
TransitResults: transitResults,
|
||||||
|
VehicleResults: vehicleResults,
|
||||||
|
Searched: searched,
|
||||||
|
DriverJourneys: solidarityTransportResults,
|
||||||
|
Drivers: drivers,
|
||||||
|
OrganizedCarpools: organizedCarpoolResults,
|
||||||
|
KnowledgeBaseResults: knowledgeBaseResults,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) vehicleRequest(vehiclech chan fleetsstorage.Vehicle, start time.Time, end time.Time) {
|
||||||
|
defer close(vehiclech)
|
||||||
|
vehiclerequest := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
}
|
||||||
|
vehicleresp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), vehiclerequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, vehicle := range vehicleresp.Vehicles {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
if v.Free(start, end) {
|
||||||
|
vehiclech <- v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSearch saves a group's search to the saved-search microservice
|
||||||
|
func (h *ApplicationHandler) SaveSearch(
|
||||||
|
ctx context.Context,
|
||||||
|
groupID string,
|
||||||
|
departureDateTime time.Time,
|
||||||
|
departureGeo *geojson.Feature,
|
||||||
|
destinationGeo *geojson.Feature,
|
||||||
|
additionalData map[string]interface{},
|
||||||
|
) error {
|
||||||
|
// Convert geojson.Feature to proto format
|
||||||
|
var protoDepart, protoDest *savedsearchproto.SavedSearchGeoJsonFeature
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Bool("departure_nil", departureGeo == nil).
|
||||||
|
Bool("destination_nil", destinationGeo == nil).
|
||||||
|
Msg("SaveSearch: checking geo features")
|
||||||
|
|
||||||
|
if departureGeo != nil {
|
||||||
|
departureBytes, err := departureGeo.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling departure: %w", err)
|
||||||
|
}
|
||||||
|
protoDepart = &savedsearchproto.SavedSearchGeoJsonFeature{
|
||||||
|
Serialized: string(departureBytes),
|
||||||
|
}
|
||||||
|
log.Debug().Str("departure_json", string(departureBytes)).Msg("SaveSearch: departure converted")
|
||||||
|
}
|
||||||
|
|
||||||
|
if destinationGeo != nil {
|
||||||
|
destinationBytes, err := destinationGeo.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error marshaling destination: %w", err)
|
||||||
|
}
|
||||||
|
protoDest = &savedsearchproto.SavedSearchGeoJsonFeature{
|
||||||
|
Serialized: string(destinationBytes),
|
||||||
|
}
|
||||||
|
log.Debug().Str("destination_json", string(destinationBytes)).Msg("SaveSearch: destination converted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert additional data to protobuf Struct
|
||||||
|
var protoData *structpb.Struct
|
||||||
|
if additionalData != nil && len(additionalData) > 0 {
|
||||||
|
var err error
|
||||||
|
protoData, err = structpb.NewStruct(additionalData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting additional data: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle zero time value
|
||||||
|
var protoDateTime *timestamppb.Timestamp
|
||||||
|
if !departureDateTime.IsZero() {
|
||||||
|
protoDateTime = timestamppb.New(departureDateTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the saved-search service
|
||||||
|
_, err := h.services.GRPC.SavedSearch.CreateSavedSearch(ctx, &savedsearchproto.CreateSavedSearchRequest{
|
||||||
|
OwnerId: groupID,
|
||||||
|
Departure: protoDepart,
|
||||||
|
Destination: protoDest,
|
||||||
|
Datetime: protoDateTime,
|
||||||
|
Data: protoData,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error calling saved-search service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("group_id", groupID).Msg("search saved successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSavedSearchesByOwner retrieves saved searches for a group
|
||||||
|
func (h *ApplicationHandler) GetSavedSearchesByOwner(
|
||||||
|
ctx context.Context,
|
||||||
|
groupID string,
|
||||||
|
) ([]*savedsearchtypes.SavedSearch, error) {
|
||||||
|
// Call the saved-search service to get searches by owner
|
||||||
|
response, err := h.services.GRPC.SavedSearch.GetSavedSearchesByOwner(ctx, &savedsearchproto.GetSavedSearchesByOwnerRequest{
|
||||||
|
OwnerId: groupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error calling saved-search service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert protobuf searches to domain types
|
||||||
|
var searches []*savedsearchtypes.SavedSearch
|
||||||
|
for _, protoSearch := range response.SavedSearches {
|
||||||
|
search, err := savedsearchtransformers.SavedSearchProtoToType(protoSearch)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("search_id", protoSearch.Id).Msg("failed to convert saved search")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
searches = append(searches, search)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort searches by datetime (earliest first)
|
||||||
|
sort.Slice(searches, func(i, j int) bool {
|
||||||
|
return searches[i].DateTime.Before(searches[j].DateTime)
|
||||||
|
})
|
||||||
|
|
||||||
|
return searches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSavedSearch deletes a saved search by ID for the specified owner
|
||||||
|
func (h *ApplicationHandler) DeleteSavedSearch(
|
||||||
|
ctx context.Context,
|
||||||
|
searchID string,
|
||||||
|
ownerID string,
|
||||||
|
) error {
|
||||||
|
// Call the saved-search service to delete the search
|
||||||
|
_, err := h.services.GRPC.SavedSearch.DeleteSavedSearch(ctx, &savedsearchproto.DeleteSavedSearchRequest{
|
||||||
|
Id: searchID,
|
||||||
|
OwnerId: ownerID, // For authorization - ensure only the owner can delete
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error calling saved-search service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("search_id", searchID).Str("owner_id", ownerID).Msg("saved search deleted successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/form-validators"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MembersResult struct {
|
||||||
|
Accounts []mobilityaccountsstorage.Account
|
||||||
|
CacheID string
|
||||||
|
GroupsNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetMembers(ctx context.Context) (*MembersResult, error) {
|
||||||
|
accounts, err := h.services.GetAccounts()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupsNames []string
|
||||||
|
|
||||||
|
for _, v := range accounts {
|
||||||
|
adminid := v.ID
|
||||||
|
request := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: adminid,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var allIds []string
|
||||||
|
for _, v := range resp.Account.ToStorageType().Data["groups"].([]any) {
|
||||||
|
s := fmt.Sprintf("%v", v)
|
||||||
|
if !(strings.Contains(s, "admin")) {
|
||||||
|
allIds = append(allIds, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reques := &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: allIds,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(ctx, reques)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := ""
|
||||||
|
for _, group := range res.Groups {
|
||||||
|
g += fmt.Sprintf("%v", group.ToStorageType().Data["name"]) + " "
|
||||||
|
}
|
||||||
|
groupsNames = append(groupsNames, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheID := uuid.NewString()
|
||||||
|
h.cache.PutWithTTL(cacheID, accounts, 1*time.Hour)
|
||||||
|
|
||||||
|
return &MembersResult{
|
||||||
|
Accounts: accounts,
|
||||||
|
CacheID: cacheID,
|
||||||
|
GroupsNames: groupsNames,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemberDataResult struct {
|
||||||
|
Account mobilityaccountsstorage.Account
|
||||||
|
GroupsNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetMemberData(ctx context.Context, memberID string) (*MemberDataResult, error) {
|
||||||
|
request := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: memberID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security check: ensure this is actually a member account
|
||||||
|
if resp.Account.Namespace != "parcoursmob" {
|
||||||
|
return nil, fmt.Errorf("account %s is not a member (namespace: %s)", memberID, resp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allIds []string
|
||||||
|
for _, v := range resp.Account.ToStorageType().Data["groups"].([]any) {
|
||||||
|
s := fmt.Sprintf("%v", v)
|
||||||
|
if !(strings.Contains(s, "admin")) {
|
||||||
|
allIds = append(allIds, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reques := &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: allIds,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(ctx, reques)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupsNames []string
|
||||||
|
for _, group := range res.Groups {
|
||||||
|
g := fmt.Sprintf("%v", group.ToStorageType().Data["name"])
|
||||||
|
groupsNames = append(groupsNames, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MemberDataResult{
|
||||||
|
Account: resp.Account.ToStorageType(),
|
||||||
|
GroupsNames: groupsNames,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemberResult struct {
|
||||||
|
Account mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetMember(ctx context.Context, memberID string) (*MemberResult, error) {
|
||||||
|
request := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: memberID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security check: ensure this is actually a member account
|
||||||
|
if resp.Account.Namespace != "parcoursmob" {
|
||||||
|
return nil, fmt.Errorf("account %s is not a member (namespace: %s)", memberID, resp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MemberResult{
|
||||||
|
Account: resp.Account.ToStorageType(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UpdateMember(ctx context.Context, memberID, firstName, lastName, email, phoneNumber, gender string) (string, error) {
|
||||||
|
// Security check: verify the account exists and is a member
|
||||||
|
getRequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: memberID,
|
||||||
|
}
|
||||||
|
getResp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if getResp.Account.Namespace != "parcoursmob" {
|
||||||
|
return "", fmt.Errorf("account %s is not a member (namespace: %s)", memberID, getResp.Account.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataMap := map[string]any{
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
"email": email,
|
||||||
|
"phone_number": phoneNumber,
|
||||||
|
"gender": gender,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the data
|
||||||
|
formData := UserForm{
|
||||||
|
FirstName: firstName,
|
||||||
|
LastName: lastName,
|
||||||
|
Email: email,
|
||||||
|
PhoneNumber: phoneNumber,
|
||||||
|
Gender: gender,
|
||||||
|
}
|
||||||
|
|
||||||
|
validate := formvalidators.New()
|
||||||
|
if err := validate.Struct(formData); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &mobilityaccounts.UpdateDataRequest{
|
||||||
|
Account: &mobilityaccounts.Account{
|
||||||
|
Id: memberID,
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Account.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserForm struct {
|
||||||
|
FirstName string `json:"first_name" validate:"required"`
|
||||||
|
LastName string `json:"last_name" validate:"required"`
|
||||||
|
Email string `json:"email" validate:"required,email"`
|
||||||
|
PhoneNumber string `json:"phone_number" `
|
||||||
|
Address any `json:"address,omitempty"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterUserResult struct {
|
||||||
|
UserID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) RegisterUser(ctx context.Context, user mobilityaccountsstorage.Account) (*RegisterUserResult, error) {
|
||||||
|
account, err := mobilityaccounts.AccountFromStorageType(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.MobilityAccounts.Register(ctx, &mobilityaccounts.RegisterRequest{
|
||||||
|
Account: account,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, ok := user.Metadata["import_in_group"]; ok {
|
||||||
|
if group, ok := g.(string); ok {
|
||||||
|
_, err = h.services.GRPC.GroupsManagement.Subscribe(ctx, &groupsmanagement.SubscribeRequest{
|
||||||
|
Groupid: group,
|
||||||
|
Memberid: resp.Account.Id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RegisterUserResult{
|
||||||
|
UserID: resp.Account.Id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,31 +1,14 @@
|
||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *ApplicationHandler) SendSMS(w http.ResponseWriter, r *http.Request) {
|
func (h *ApplicationHandler) SendSMS(ctx context.Context, beneficiaryID, message string) error {
|
||||||
if r.Method != "POST" {
|
return h.GenerateSMS(beneficiaryID, message)
|
||||||
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")
|
|
||||||
|
|
||||||
h.GenerateSMS(beneficiaryid, message)
|
|
||||||
|
|
||||||
http.Redirect(w, r, referer, http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ApplicationHandler) GenerateSMS(recipientid string, message string) error {
|
func (h *ApplicationHandler) GenerateSMS(recipientid string, message string) error {
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,33 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) SendSupportMessage(ctx context.Context, comment, userEmail string) error {
|
||||||
|
data := map[string]any{
|
||||||
|
"key": comment,
|
||||||
|
"user": userEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
supportEmail := h.config.GetString("modules.support.email")
|
||||||
|
if supportEmail == "" {
|
||||||
|
supportEmail = "support@mobicoop.fr"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("user_email", userEmail).Str("support_email", supportEmail).Msg("Sending support message")
|
||||||
|
|
||||||
|
if err := h.emailing.Send("support.request", supportEmail, data); err != nil {
|
||||||
|
return fmt.Errorf("failed to send support email: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,600 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VehiclesManagementOverviewResult struct {
|
||||||
|
Vehicles []fleetsstorage.Vehicle
|
||||||
|
VehiclesMap map[string]fleetsstorage.Vehicle
|
||||||
|
DriversMap map[string]mobilityaccountsstorage.Account
|
||||||
|
Bookings []fleetsstorage.Booking
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehiclesManagementOverview(ctx context.Context, groupID string) (*VehiclesManagementOverviewResult, error) {
|
||||||
|
request := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicles(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vehicles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicles := []fleetsstorage.Vehicle{}
|
||||||
|
bookings := []fleetsstorage.Booking{}
|
||||||
|
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
||||||
|
|
||||||
|
for _, vehicle := range resp.Vehicles {
|
||||||
|
if h.filterVehicleByGroup(vehicle, groupID) {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
vehicleBookings := []fleetsstorage.Booking{}
|
||||||
|
for _, b := range v.Bookings {
|
||||||
|
log.Debug().Any("booking", b).Msg("debug")
|
||||||
|
if b.Status() != fleetsstorage.StatusOld {
|
||||||
|
if !b.Deleted {
|
||||||
|
bookings = append(bookings, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.Unavailableto.After(time.Now()) {
|
||||||
|
vehicleBookings = append(vehicleBookings, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Bookings = vehicleBookings
|
||||||
|
vehicles = append(vehicles, v)
|
||||||
|
vehiclesMap[v.ID] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
driversMap, _ := h.services.GetBeneficiariesMap()
|
||||||
|
|
||||||
|
sort.Sort(sorting.VehiclesByLicencePlate(vehicles))
|
||||||
|
sort.Sort(sorting.BookingsByStartdate(bookings))
|
||||||
|
|
||||||
|
return &VehiclesManagementOverviewResult{
|
||||||
|
Vehicles: vehicles,
|
||||||
|
VehiclesMap: vehiclesMap,
|
||||||
|
DriversMap: driversMap,
|
||||||
|
Bookings: bookings,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) filterVehicleByGroup(v *fleets.Vehicle, groupID string) bool {
|
||||||
|
if groupID == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range v.Administrators {
|
||||||
|
if n == groupID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type VehiclesManagementBookingsListResult struct {
|
||||||
|
VehiclesMap map[string]fleetsstorage.Vehicle
|
||||||
|
DriversMap map[string]mobilityaccountsstorage.Account
|
||||||
|
Bookings []fleetsstorage.Booking
|
||||||
|
CacheID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehiclesManagementBookingsList(ctx context.Context, groupID, status, startDate, endDate string) (*VehiclesManagementBookingsListResult, error) {
|
||||||
|
request := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
IncludeDeleted: true,
|
||||||
|
}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicles(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vehicles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bookings := []fleetsstorage.Booking{}
|
||||||
|
vehiclesMap := map[string]fleetsstorage.Vehicle{}
|
||||||
|
|
||||||
|
// Parse start date filter
|
||||||
|
var startdate time.Time
|
||||||
|
if startDate != "" {
|
||||||
|
if parsed, err := time.Parse("2006-01-02", startDate); err == nil {
|
||||||
|
startdate = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse end date filter
|
||||||
|
var enddate time.Time
|
||||||
|
if endDate != "" {
|
||||||
|
if parsed, err := time.Parse("2006-01-02", endDate); err == nil {
|
||||||
|
enddate = parsed.Add(24 * time.Hour) // End of day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vehicle := range resp.Vehicles {
|
||||||
|
if h.filterVehicleByGroup(vehicle, groupID) {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
vehiclesMap[v.ID] = v
|
||||||
|
for _, b := range v.Bookings {
|
||||||
|
if v, ok := b.Data["administrator_unavailability"].(bool); !ok || !v {
|
||||||
|
// Apply status filter
|
||||||
|
if status != "" {
|
||||||
|
bookingStatus := b.Status()
|
||||||
|
statusInt := 0
|
||||||
|
|
||||||
|
if b.Deleted {
|
||||||
|
statusInt = -2 // Use -2 for cancelled to distinguish from terminated
|
||||||
|
} else {
|
||||||
|
statusInt = bookingStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map status string to int
|
||||||
|
var filterStatusInt int
|
||||||
|
switch status {
|
||||||
|
case "FORTHCOMING":
|
||||||
|
filterStatusInt = 1
|
||||||
|
case "ONGOING":
|
||||||
|
filterStatusInt = 0
|
||||||
|
case "TERMINATED":
|
||||||
|
filterStatusInt = -1
|
||||||
|
case "CANCELLED":
|
||||||
|
filterStatusInt = -2
|
||||||
|
default:
|
||||||
|
filterStatusInt = 999 // Invalid status, won't match anything
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusInt != filterStatusInt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply date filter (on startdate)
|
||||||
|
if !startdate.IsZero() && b.Startdate.Before(startdate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !enddate.IsZero() && b.Startdate.After(enddate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bookings = append(bookings, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.BookingsByStartdate(bookings))
|
||||||
|
|
||||||
|
cacheID := uuid.NewString()
|
||||||
|
h.cache.PutWithTTL(cacheID, bookings, 1*time.Hour)
|
||||||
|
|
||||||
|
driversMap, _ := h.services.GetBeneficiariesMap()
|
||||||
|
|
||||||
|
return &VehiclesManagementBookingsListResult{
|
||||||
|
VehiclesMap: vehiclesMap,
|
||||||
|
DriversMap: driversMap,
|
||||||
|
Bookings: bookings,
|
||||||
|
CacheID: cacheID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) CreateVehicle(ctx context.Context, name, vehicleType, informations, licencePlate, kilometers string, automatic bool, address map[string]any, otherProperties map[string]any) (string, error) {
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return "", fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
group := g.(storage.Group)
|
||||||
|
|
||||||
|
dataMap := map[string]any{}
|
||||||
|
if name != "" {
|
||||||
|
dataMap["name"] = name
|
||||||
|
}
|
||||||
|
if address != nil {
|
||||||
|
dataMap["address"] = address
|
||||||
|
}
|
||||||
|
if informations != "" {
|
||||||
|
dataMap["informations"] = informations
|
||||||
|
}
|
||||||
|
if licencePlate != "" {
|
||||||
|
dataMap["licence_plate"] = licencePlate
|
||||||
|
}
|
||||||
|
dataMap["automatic"] = automatic
|
||||||
|
if kilometers != "" {
|
||||||
|
dataMap["kilometers"] = kilometers
|
||||||
|
}
|
||||||
|
// Add other properties
|
||||||
|
for key, value := range otherProperties {
|
||||||
|
dataMap[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create data struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle := &fleets.Vehicle{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Namespace: "parcoursmob",
|
||||||
|
Type: vehicleType,
|
||||||
|
Administrators: []string{group.ID},
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &fleets.AddVehicleRequest{
|
||||||
|
Vehicle: vehicle,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Fleets.AddVehicle(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to add vehicle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vehicle.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehicleTypes(ctx context.Context) ([]string, error) {
|
||||||
|
return h.config.GetStringSlice("modules.fleets.vehicle_types"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type VehicleDisplayResult struct {
|
||||||
|
Vehicle fleetsstorage.Vehicle
|
||||||
|
Beneficiaries map[string]mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehicleDisplay(ctx context.Context, vehicleID string) (*VehicleDisplayResult, error) {
|
||||||
|
request := &fleets.GetVehicleRequest{
|
||||||
|
Vehicleid: vehicleID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicle(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vehicle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaries, err := h.services.GetBeneficiariesMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get beneficiaries: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle := resp.Vehicle.ToStorageType()
|
||||||
|
|
||||||
|
// Sort bookings by start date (most recent first)
|
||||||
|
sort.Slice(vehicle.Bookings, func(i, j int) bool {
|
||||||
|
return vehicle.Bookings[i].Startdate.After(vehicle.Bookings[j].Startdate)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &VehicleDisplayResult{
|
||||||
|
Vehicle: vehicle,
|
||||||
|
Beneficiaries: beneficiaries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID, startdate, enddate, unavailablefrom, unavailableto string) error {
|
||||||
|
booking, err := h.services.GetBooking(bookingID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get booking: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newbooking, _ := fleets.BookingFromStorageType(&booking)
|
||||||
|
|
||||||
|
if startdate != "" {
|
||||||
|
newstartdate, _ := time.Parse("2006-01-02", startdate)
|
||||||
|
newbooking.Startdate = timestamppb.New(newstartdate)
|
||||||
|
|
||||||
|
if newstartdate.Before(newbooking.Unavailablefrom.AsTime()) {
|
||||||
|
newbooking.Unavailablefrom = timestamppb.New(newstartdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enddate != "" {
|
||||||
|
newenddate, _ := time.Parse("2006-01-02", enddate)
|
||||||
|
newbooking.Enddate = timestamppb.New(newenddate)
|
||||||
|
|
||||||
|
if newenddate.After(newbooking.Unavailableto.AsTime()) || newenddate.Equal(newbooking.Unavailableto.AsTime()) {
|
||||||
|
newbooking.Unavailableto = timestamppb.New(newenddate.Add(24 * time.Hour))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unavailablefrom != "" {
|
||||||
|
newunavailablefrom, _ := time.Parse("2006-01-02", unavailablefrom)
|
||||||
|
newbooking.Unavailablefrom = timestamppb.New(newunavailablefrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if unavailableto != "" {
|
||||||
|
newunavailableto, _ := time.Parse("2006-01-02", unavailableto)
|
||||||
|
newbooking.Unavailableto = timestamppb.New(newunavailableto)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &fleets.UpdateBookingRequest{
|
||||||
|
Booking: newbooking,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Fleets.UpdateBooking(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookingDisplayResult struct {
|
||||||
|
Booking fleetsstorage.Booking
|
||||||
|
Vehicle fleetsstorage.Vehicle
|
||||||
|
Beneficiary mobilityaccountsstorage.Account
|
||||||
|
Group storage.Group
|
||||||
|
Documents []filestorage.FileInfo
|
||||||
|
FileTypesMap map[string]string
|
||||||
|
Alternatives []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBookingDisplay(ctx context.Context, bookingID string) (*BookingDisplayResult, error) {
|
||||||
|
booking, err := h.services.GetBooking(bookingID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get booking: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiary := mobilityaccountsstorage.Account{}
|
||||||
|
if booking.Driver != "" {
|
||||||
|
beneficiaryrequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: booking.Driver,
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, beneficiaryrequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get beneficiary: %w", err)
|
||||||
|
}
|
||||||
|
beneficiary = beneficiaryresp.Account.ToStorageType()
|
||||||
|
}
|
||||||
|
|
||||||
|
grouprequest := &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: booking.Vehicle.Administrators[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alternativerequest := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
Types: []string{booking.Vehicle.Type},
|
||||||
|
Administrators: booking.Vehicle.Administrators,
|
||||||
|
AvailabilityFrom: timestamppb.New(booking.Startdate),
|
||||||
|
AvailabilityTo: timestamppb.New(booking.Enddate.Add(24 * time.Hour)),
|
||||||
|
}
|
||||||
|
|
||||||
|
alternativeresp, err := h.services.GRPC.Fleets.GetVehicles(ctx, alternativerequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to get alternative vehicles")
|
||||||
|
}
|
||||||
|
|
||||||
|
alternatives := []any{}
|
||||||
|
for _, a := range alternativeresp.Vehicles {
|
||||||
|
alternatives = append(alternatives, a.ToStorageType())
|
||||||
|
}
|
||||||
|
|
||||||
|
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
||||||
|
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
||||||
|
|
||||||
|
return &BookingDisplayResult{
|
||||||
|
Booking: booking,
|
||||||
|
Vehicle: booking.Vehicle,
|
||||||
|
Beneficiary: beneficiary,
|
||||||
|
Group: groupresp.Group.ToStorageType(),
|
||||||
|
Documents: documents,
|
||||||
|
FileTypesMap: fileTypesMap,
|
||||||
|
Alternatives: alternatives,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) ChangeBookingVehicle(ctx context.Context, bookingID, newVehicleID string) error {
|
||||||
|
booking, err := h.services.GetBooking(bookingID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get booking: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
booking.Vehicleid = newVehicleID
|
||||||
|
b, _ := fleets.BookingFromStorageType(&booking)
|
||||||
|
|
||||||
|
request := &fleets.UpdateBookingRequest{
|
||||||
|
Booking: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Fleets.UpdateBooking(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) MakeVehicleUnavailable(ctx context.Context, vehicleID, unavailablefrom, unavailableto, comment, currentUserID string, currentUserClaims map[string]any) error {
|
||||||
|
g := ctx.Value(identification.GroupKey)
|
||||||
|
if g == nil {
|
||||||
|
return fmt.Errorf("no group found in context")
|
||||||
|
}
|
||||||
|
currentGroup := g.(storage.Group)
|
||||||
|
|
||||||
|
unavailablefromTime, _ := time.Parse("2006-01-02", unavailablefrom)
|
||||||
|
unavailabletoTime, _ := time.Parse("2006-01-02", unavailableto)
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"comment": comment,
|
||||||
|
"administrator_unavailability": true,
|
||||||
|
"booked_by": map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"id": currentUserID,
|
||||||
|
"display_name": currentUserClaims["display_name"],
|
||||||
|
},
|
||||||
|
"group": map[string]any{
|
||||||
|
"id": currentGroup.ID,
|
||||||
|
"name": currentGroup.Data["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
datapb, err := structpb.NewStruct(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create data struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
booking := &fleets.Booking{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Vehicleid: vehicleID,
|
||||||
|
Unavailablefrom: timestamppb.New(unavailablefromTime),
|
||||||
|
Unavailableto: timestamppb.New(unavailabletoTime),
|
||||||
|
Data: datapb,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &fleets.CreateBookingRequest{
|
||||||
|
Booking: booking,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Fleets.CreateBooking(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) DeleteBooking(ctx context.Context, bookingID string) error {
|
||||||
|
request := &fleets.DeleteBookingRequest{
|
||||||
|
Id: bookingID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := h.services.GRPC.Fleets.DeleteBooking(ctx, request)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBookingForUnbooking(ctx context.Context, bookingID string) (fleetsstorage.Booking, error) {
|
||||||
|
request := &fleets.GetBookingRequest{
|
||||||
|
Bookingid: bookingID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetBooking(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return fleetsstorage.Booking{}, fmt.Errorf("failed to get booking: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Booking.ToStorageType(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UnbookVehicle(ctx context.Context, bookingID, motif, currentUserID string, currentUserClaims map[string]any, currentGroup any) error {
|
||||||
|
group := currentGroup.(storage.Group)
|
||||||
|
|
||||||
|
// Prepare deletion metadata (microservice will add deleted_at automatically)
|
||||||
|
deletionMetadata := map[string]any{
|
||||||
|
"deleted_by": map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"id": currentUserID,
|
||||||
|
"display_name": currentUserClaims["first_name"].(string) + " " + currentUserClaims["last_name"].(string),
|
||||||
|
"email": currentUserClaims["email"],
|
||||||
|
},
|
||||||
|
"group": map[string]any{
|
||||||
|
"id": group.ID,
|
||||||
|
"name": group.Data["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"reason": motif,
|
||||||
|
}
|
||||||
|
|
||||||
|
deletionMetadataPb, err := structpb.NewStruct(deletionMetadata)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create deletion metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the microservice's delete endpoint with metadata
|
||||||
|
deleteRequest := &fleets.DeleteBookingRequest{
|
||||||
|
Id: bookingID,
|
||||||
|
DeletionMetadata: deletionMetadataPb,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Fleets.DeleteBooking(ctx, deleteRequest)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type VehicleForUpdateResult struct {
|
||||||
|
Vehicle fleetsstorage.Vehicle
|
||||||
|
VehicleTypes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehicleForUpdate(ctx context.Context, vehicleID string) (*VehicleForUpdateResult, error) {
|
||||||
|
request := &fleets.GetVehicleRequest{
|
||||||
|
Vehicleid: vehicleID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicle(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vehicle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicleTypes := h.config.GetStringSlice("modules.fleets.vehicle_types")
|
||||||
|
|
||||||
|
return &VehicleForUpdateResult{
|
||||||
|
Vehicle: resp.Vehicle.ToStorageType(),
|
||||||
|
VehicleTypes: vehicleTypes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) UpdateVehicle(ctx context.Context, vehicleID, name, vehicleType, informations, licencePlate, kilometers string, automatic bool, address map[string]any, otherProperties map[string]any) (string, error) {
|
||||||
|
getRequest := &fleets.GetVehicleRequest{
|
||||||
|
Vehicleid: vehicleID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicle(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get vehicle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with existing data to preserve all fields
|
||||||
|
dataMap := resp.Vehicle.Data.AsMap()
|
||||||
|
if dataMap == nil {
|
||||||
|
dataMap = map[string]any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update with new values
|
||||||
|
if name != "" {
|
||||||
|
dataMap["name"] = name
|
||||||
|
}
|
||||||
|
if address != nil {
|
||||||
|
dataMap["address"] = address
|
||||||
|
}
|
||||||
|
if informations != "" {
|
||||||
|
dataMap["informations"] = informations
|
||||||
|
}
|
||||||
|
if licencePlate != "" {
|
||||||
|
dataMap["licence_plate"] = licencePlate
|
||||||
|
}
|
||||||
|
if kilometers != "" {
|
||||||
|
dataMap["kilometers"] = kilometers
|
||||||
|
}
|
||||||
|
dataMap["automatic"] = automatic
|
||||||
|
// Add other properties
|
||||||
|
for key, value := range otherProperties {
|
||||||
|
dataMap[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := structpb.NewValue(dataMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create data struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRequest := &fleets.UpdateVehicleRequest{
|
||||||
|
Vehicle: &fleets.Vehicle{
|
||||||
|
Id: vehicleID,
|
||||||
|
Namespace: resp.Vehicle.Namespace,
|
||||||
|
Type: vehicleType,
|
||||||
|
Administrators: resp.Vehicle.Administrators,
|
||||||
|
Data: data.GetStructValue(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
updateResp, err := h.services.GRPC.Fleets.UpdateVehicle(ctx, updateRequest)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to update vehicle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateResp.Vehicle.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,393 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/sorting"
|
||||||
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||||
|
"git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||||
|
groupsmanagementstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/paulmach/orb/geojson"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VehiclesSearchResult struct {
|
||||||
|
Vehicles []storage.Vehicle
|
||||||
|
Beneficiary mobilityaccountsstorage.Account
|
||||||
|
BeneficiaryDocuments []filestorage.FileInfo
|
||||||
|
Groups map[string]any
|
||||||
|
Searched bool
|
||||||
|
StartDate string
|
||||||
|
EndDate string
|
||||||
|
VehicleType string
|
||||||
|
Automatic bool
|
||||||
|
MandatoryDocuments []string
|
||||||
|
FileTypesMap map[string]string
|
||||||
|
VehicleTypes []string
|
||||||
|
Beneficiaries []mobilityaccountsstorage.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID, startDateStr, endDateStr, vehicleType string, automatic bool) (*VehiclesSearchResult, error) {
|
||||||
|
var beneficiary mobilityaccountsstorage.Account
|
||||||
|
beneficiarydocuments := []filestorage.FileInfo{}
|
||||||
|
vehicles := []storage.Vehicle{}
|
||||||
|
searched := false
|
||||||
|
administrators := []string{}
|
||||||
|
|
||||||
|
startdate, err := time.Parse("2006-01-02", startDateStr)
|
||||||
|
if err != nil {
|
||||||
|
startdate = time.Time{}
|
||||||
|
}
|
||||||
|
enddate, err := time.Parse("2006-01-02", endDateStr)
|
||||||
|
if err != nil {
|
||||||
|
enddate = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if beneficiaryID != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) {
|
||||||
|
// Handler form
|
||||||
|
searched = true
|
||||||
|
|
||||||
|
requestbeneficiary := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: beneficiaryID,
|
||||||
|
}
|
||||||
|
|
||||||
|
respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, requestbeneficiary)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get beneficiary: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiary = respbeneficiary.Account.ToStorageType()
|
||||||
|
|
||||||
|
request := &fleets.GetVehiclesRequest{
|
||||||
|
Namespaces: []string{"parcoursmob"},
|
||||||
|
AvailabilityFrom: timestamppb.New(startdate),
|
||||||
|
AvailabilityTo: timestamppb.New(enddate),
|
||||||
|
}
|
||||||
|
|
||||||
|
if vehicleType != "" {
|
||||||
|
request.Types = []string{vehicleType}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetVehicles(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vehicles: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vehicle := range resp.Vehicles {
|
||||||
|
v := vehicle.ToStorageType()
|
||||||
|
|
||||||
|
if vehicleType == "Voiture" && automatic {
|
||||||
|
if auto, ok := v.Data["automatic"].(bool); !ok || !auto {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adminfound := false
|
||||||
|
for _, a := range administrators {
|
||||||
|
if a == v.Administrators[0] {
|
||||||
|
adminfound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !adminfound {
|
||||||
|
administrators = append(administrators, v.Administrators[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicles = append(vehicles, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort vehicles if beneficiary address is set
|
||||||
|
if beneficiaryAddress, ok := beneficiary.Data["address"]; ok {
|
||||||
|
beneficiaryAddressJson, err := json.Marshal(beneficiaryAddress)
|
||||||
|
if err == nil {
|
||||||
|
beneficiaryAddressGeojson, err := geojson.UnmarshalFeature(beneficiaryAddressJson)
|
||||||
|
if err == nil {
|
||||||
|
slices.SortFunc(vehicles, sorting.VehiclesByDistanceFrom(*beneficiaryAddressGeojson))
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Msg("error transforming beneficiary address to GeoJSON")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Msg("error transforming beneficiary address to JSON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiarydocuments = h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiary.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts, err := h.services.GetBeneficiariesMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get beneficiaries: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to slice for compatibility
|
||||||
|
beneficiaries := make([]mobilityaccountsstorage.Account, 0, len(accounts))
|
||||||
|
for _, account := range accounts {
|
||||||
|
beneficiaries = append(beneficiaries, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := map[string]any{}
|
||||||
|
if len(administrators) > 0 {
|
||||||
|
admingroups, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(ctx, &groupsmanagement.GetGroupsBatchRequest{
|
||||||
|
Groupids: administrators,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get admin groups: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range admingroups.Groups {
|
||||||
|
groups[g.Id] = g.ToStorageType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sorting.BeneficiariesByName(beneficiaries))
|
||||||
|
|
||||||
|
mandatoryDocuments := h.config.GetStringSlice("modules.fleets.booking_documents.mandatory")
|
||||||
|
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
||||||
|
vehicleTypes := h.config.GetStringSlice("modules.fleets.vehicle_types")
|
||||||
|
|
||||||
|
return &VehiclesSearchResult{
|
||||||
|
Vehicles: vehicles,
|
||||||
|
Beneficiary: beneficiary,
|
||||||
|
BeneficiaryDocuments: beneficiarydocuments,
|
||||||
|
Groups: groups,
|
||||||
|
Searched: searched,
|
||||||
|
StartDate: startDateStr,
|
||||||
|
EndDate: endDateStr,
|
||||||
|
VehicleType: vehicleType,
|
||||||
|
Automatic: automatic,
|
||||||
|
MandatoryDocuments: mandatoryDocuments,
|
||||||
|
FileTypesMap: fileTypesMap,
|
||||||
|
VehicleTypes: vehicleTypes,
|
||||||
|
Beneficiaries: beneficiaries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookVehicleResult struct {
|
||||||
|
BookingID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, beneficiaryID, startDateStr, endDateStr string, documents map[string]io.Reader, documentHeaders map[string]string, existingDocs map[string]string, currentUserID string, currentUserClaims map[string]any, currentGroup any) (*BookVehicleResult, error) {
|
||||||
|
group := currentGroup.(groupsmanagementstorage.Group)
|
||||||
|
|
||||||
|
vehicle, err := h.services.GRPC.Fleets.GetVehicle(ctx, &fleets.GetVehicleRequest{
|
||||||
|
Vehicleid: vehicleID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("vehicle not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
startdate, err := time.Parse("2006-01-02", startDateStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid start date: %w", err)
|
||||||
|
}
|
||||||
|
enddate, err := time.Parse("2006-01-02", endDateStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid end date: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"booked_by": map[string]any{
|
||||||
|
"user": map[string]any{
|
||||||
|
"id": currentUserID,
|
||||||
|
"display_name": fmt.Sprintf("%s %s", currentUserClaims["first_name"], currentUserClaims["last_name"]),
|
||||||
|
},
|
||||||
|
"group": map[string]any{
|
||||||
|
"id": group.ID,
|
||||||
|
"name": group.Data["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
datapb, err := structpb.NewStruct(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create booking metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bookingID := uuid.NewString()
|
||||||
|
booking := &fleets.Booking{
|
||||||
|
Id: bookingID,
|
||||||
|
Vehicleid: vehicleID,
|
||||||
|
Driver: beneficiaryID,
|
||||||
|
Startdate: timestamppb.New(startdate),
|
||||||
|
Enddate: timestamppb.New(enddate),
|
||||||
|
Unavailablefrom: timestamppb.New(startdate),
|
||||||
|
Unavailableto: timestamppb.New(enddate.Add(72 * time.Hour)),
|
||||||
|
Data: datapb,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &fleets.CreateBookingRequest{
|
||||||
|
Booking: booking,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle document uploads
|
||||||
|
for docType, file := range documents {
|
||||||
|
fileid := uuid.NewString()
|
||||||
|
filename := documentHeaders[docType]
|
||||||
|
|
||||||
|
metadata := map[string]string{
|
||||||
|
"type": docType,
|
||||||
|
"name": filename,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.filestorage.Put(file, filestorage.PREFIX_BOOKINGS, fmt.Sprintf("%s/%s_%s", bookingID, fileid, filename), -1, metadata); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to upload document %s: %w", docType, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle existing documents
|
||||||
|
for docType, existingFile := range existingDocs {
|
||||||
|
path := strings.Split(existingFile, "/")
|
||||||
|
if err := h.filestorage.Copy(existingFile, fmt.Sprintf("%s/%s/%s", filestorage.PREFIX_BOOKINGS, bookingID, path[len(path)-1])); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to copy existing document %s: %w", docType, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.services.GRPC.Fleets.CreateBooking(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create booking: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTIFY GROUP MEMBERS
|
||||||
|
members, _, err := h.groupmembers(vehicle.Vehicle.Administrators[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to get group members for notification")
|
||||||
|
} else {
|
||||||
|
for _, m := range members {
|
||||||
|
if email, ok := m.Data["email"].(string); ok {
|
||||||
|
h.emailing.Send("fleets.bookings.creation_admin_alert", email, map[string]string{
|
||||||
|
"bookingid": bookingID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BookVehicleResult{
|
||||||
|
BookingID: bookingID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type VehicleBookingDetailsResult struct {
|
||||||
|
Booking storage.Booking
|
||||||
|
Vehicle storage.Vehicle
|
||||||
|
Beneficiary mobilityaccountsstorage.Account
|
||||||
|
Group groupsmanagementstorage.Group
|
||||||
|
Documents []filestorage.FileInfo
|
||||||
|
FileTypesMap map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehicleBookingDetails(ctx context.Context, bookingID string) (*VehicleBookingDetailsResult, error) {
|
||||||
|
request := &fleets.GetBookingRequest{
|
||||||
|
Bookingid: bookingID,
|
||||||
|
}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetBooking(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get booking: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
booking := resp.Booking.ToStorageType()
|
||||||
|
|
||||||
|
beneficiaryrequest := &mobilityaccounts.GetAccountRequest{
|
||||||
|
Id: booking.Driver,
|
||||||
|
}
|
||||||
|
|
||||||
|
beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(ctx, beneficiaryrequest)
|
||||||
|
if err != nil {
|
||||||
|
beneficiaryresp = &mobilityaccounts.GetAccountResponse{
|
||||||
|
Account: &mobilityaccounts.Account{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grouprequest := &groupsmanagement.GetGroupRequest{
|
||||||
|
Id: booking.Vehicle.Administrators[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(ctx, grouprequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingID)
|
||||||
|
fileTypesMap := h.config.GetStringMapString("storage.files.file_types")
|
||||||
|
|
||||||
|
return &VehicleBookingDetailsResult{
|
||||||
|
Booking: booking,
|
||||||
|
Vehicle: booking.Vehicle,
|
||||||
|
Beneficiary: beneficiaryresp.Account.ToStorageType(),
|
||||||
|
Group: groupresp.Group.ToStorageType(),
|
||||||
|
Documents: documents,
|
||||||
|
FileTypesMap: fileTypesMap,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type VehicleBookingsListResult struct {
|
||||||
|
Bookings []storage.Booking
|
||||||
|
VehiclesMap map[string]storage.Vehicle
|
||||||
|
GroupsMap map[string]groupsmanagementstorage.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetVehicleBookingsList(ctx context.Context, groupID string) (*VehicleBookingsListResult, error) {
|
||||||
|
request := &fleets.GetBookingsRequest{}
|
||||||
|
resp, err := h.services.GRPC.Fleets.GetBookings(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get bookings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bookings := []storage.Booking{}
|
||||||
|
|
||||||
|
for _, b := range resp.Bookings {
|
||||||
|
booking := b.ToStorageType()
|
||||||
|
if b1, ok := booking.Data["booked_by"].(map[string]any); ok {
|
||||||
|
if b2, ok := b1["group"].(map[string]any); ok {
|
||||||
|
if b2["id"] == groupID {
|
||||||
|
bookings = append(bookings, booking)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vehiclesMap, err := h.services.GetVehiclesMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vehicles map: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsMap, err := h.services.GetGroupsMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get groups map: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VehicleBookingsListResult{
|
||||||
|
Bookings: bookings,
|
||||||
|
VehiclesMap: vehiclesMap,
|
||||||
|
GroupsMap: groupsMap,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ApplicationHandler) GetBookingDocument(ctx context.Context, bookingID, document string) (io.Reader, string, error) {
|
||||||
|
file, info, err := h.filestorage.Get(filestorage.PREFIX_BOOKINGS, fmt.Sprintf("%s/%s", bookingID, document))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to get document: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, info.ContentType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to expose config to web handlers
|
||||||
|
func (h *ApplicationHandler) GetConfig() interface{} {
|
||||||
|
return h.config
|
||||||
|
}
|
||||||
|
|
@ -2,52 +2,14 @@ package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h ApplicationHandler) CreditWallet(w http.ResponseWriter, r *http.Request) {
|
func (h *ApplicationHandler) CreditWallet(ctx context.Context, userid string, amount float64, paymentMethod string, description string) error {
|
||||||
vars := mux.Vars(r)
|
|
||||||
userid := vars["userid"]
|
|
||||||
|
|
||||||
if r.Method != "POST" {
|
|
||||||
w.WriteHeader(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")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if paymentMethod == "" {
|
|
||||||
paymentMethod = "Paiement en espèce (MMS)"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.creditWallet(userid, amount, paymentMethod, description); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not credit wallet")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, r.Referer(), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) creditWallet(userid string, amount float64, paymentMethod string, description string) error {
|
|
||||||
account, err := h.services.GetAccount(userid)
|
account, err := h.services.GetAccount(userid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("could not retrieve account")
|
log.Error().Err(err).Msg("could not retrieve account")
|
||||||
|
|
@ -112,7 +74,7 @@ func (h *ApplicationHandler) creditWallet(userid string, amount float64, payment
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.services.GRPC.MobilityAccounts.UpdateData(context.Background(), &grpcapi.UpdateDataRequest{
|
_, err = h.services.GRPC.MobilityAccounts.UpdateData(ctx, &grpcapi.UpdateDataRequest{
|
||||||
Account: accountproto,
|
Account: accountproto,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CacheService struct {
|
||||||
|
cache storage.CacheHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCacheService(cache storage.CacheHandler) *CacheService {
|
||||||
|
return &CacheService{cache: cache}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCacheResult struct {
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CacheService) GetCacheData(cacheID string, limitsMin, limitsMax *int) (*GetCacheResult, error) {
|
||||||
|
d, err := s.cache.Get(cacheID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []any
|
||||||
|
if val, ok := d.([]any); ok {
|
||||||
|
data = val
|
||||||
|
} else {
|
||||||
|
data = []any{d}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := data
|
||||||
|
if limitsMin != nil {
|
||||||
|
min := *limitsMin
|
||||||
|
if limitsMax != nil {
|
||||||
|
max := *limitsMax
|
||||||
|
if max > len(data) {
|
||||||
|
result = data[min:]
|
||||||
|
} else {
|
||||||
|
result = data[min:max]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = data[min:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GetCacheResult{
|
||||||
|
Data: j,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLimits(limitsMinStr, limitsMaxStr string) (limitsMin, limitsMax *int) {
|
||||||
|
if limitsMinStr != "" {
|
||||||
|
if min, err := strconv.Atoi(limitsMinStr); err == nil {
|
||||||
|
limitsMin = &min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if limitsMaxStr != "" {
|
||||||
|
if max, err := strconv.Atoi(limitsMaxStr); err == nil {
|
||||||
|
limitsMax = &max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package gender
|
||||||
|
|
||||||
|
// ISO5218ToString converts ISO 5218 gender codes to French text labels
|
||||||
|
func ISO5218ToString(value string) string {
|
||||||
|
switch value {
|
||||||
|
case "0":
|
||||||
|
return "Inconnu"
|
||||||
|
case "1":
|
||||||
|
return "Masculin"
|
||||||
|
case "2":
|
||||||
|
return "Féminin"
|
||||||
|
case "9":
|
||||||
|
return "Sans objet"
|
||||||
|
default:
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package geo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeoService struct {
|
||||||
|
peliasURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeoService(peliasURL string) *GeoService {
|
||||||
|
return &GeoService{peliasURL: peliasURL}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutocompleteResult struct {
|
||||||
|
Features []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GeoService) Autocomplete(text string) (*AutocompleteResult, error) {
|
||||||
|
resp, err := http.Get(fmt.Sprintf("%s/autocomplete?text=%s", s.peliasURL, text))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to read response body")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response map[string]any
|
||||||
|
if err := json.Unmarshal(body, &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
features, ok := response["features"].([]any)
|
||||||
|
if !ok {
|
||||||
|
features = []any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AutocompleteResult{
|
||||||
|
Features: features,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
@ -13,7 +13,6 @@ const (
|
||||||
PREFIX_ORGANIZED_CARPOOL_DRIVERS = "organized_carpool/drivers"
|
PREFIX_ORGANIZED_CARPOOL_DRIVERS = "organized_carpool/drivers"
|
||||||
PREFIX_BOOKINGS = "fleets_bookings"
|
PREFIX_BOOKINGS = "fleets_bookings"
|
||||||
PREFIX_AGENDA = "event_files"
|
PREFIX_AGENDA = "event_files"
|
||||||
PREFIX_DIAGS = "diags"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
|
|
@ -29,6 +28,7 @@ type FileStorage interface {
|
||||||
List(prefix string) []FileInfo
|
List(prefix string) []FileInfo
|
||||||
Get(prefix string, file string) (io.Reader, *FileInfo, error)
|
Get(prefix string, file string) (io.Reader, *FileInfo, error)
|
||||||
Copy(src string, dest string) error
|
Copy(src string, dest string) error
|
||||||
|
Delete(prefix string, file string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileStorage(cfg *viper.Viper) (FileStorage, error) {
|
func NewFileStorage(cfg *viper.Viper) (FileStorage, error) {
|
||||||
|
|
@ -122,3 +122,12 @@ func (s *MinioStorageHandler) Copy(src string, dst string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MinioStorageHandler) Delete(prefix string, file string) error {
|
||||||
|
err := s.Client.RemoveObject(context.Background(), s.BucketName, prefix+"/"+file, minio.RemoveObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error deleting file from storage")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
84
go.mod
84
go.mod
|
|
@ -1,6 +1,6 @@
|
||||||
module git.coopgo.io/coopgo-apps/parcoursmob
|
module git.coopgo.io/coopgo-apps/parcoursmob
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.6
|
||||||
|
|
||||||
// replace git.coopgo.io/coopgo-platform/mobility-accounts => ../../coopgo-platform/mobility-accounts/
|
// replace git.coopgo.io/coopgo-platform/mobility-accounts => ../../coopgo-platform/mobility-accounts/
|
||||||
|
|
||||||
|
|
@ -16,6 +16,8 @@ go 1.24.2
|
||||||
|
|
||||||
// replace git.coopgo.io/coopgo-platform/solidarity-transport => ../../coopgo-platform/solidarity-transport/
|
// replace git.coopgo.io/coopgo-platform/solidarity-transport => ../../coopgo-platform/solidarity-transport/
|
||||||
|
|
||||||
|
// replace git.coopgo.io/coopgo-platform/saved-search => ../../coopgo-platform/saved-search/
|
||||||
|
|
||||||
// replace git.coopgo.io/coopgo-platform/carpool-service => ../../coopgo-platform/carpool-service/
|
// replace git.coopgo.io/coopgo-platform/carpool-service => ../../coopgo-platform/carpool-service/
|
||||||
|
|
||||||
// replace git.coopgo.io/coopgo-platform/multimodal-routing => ../../coopgo-platform/multimodal-routing/
|
// replace git.coopgo.io/coopgo-platform/multimodal-routing => ../../coopgo-platform/multimodal-routing/
|
||||||
|
|
@ -33,41 +35,42 @@ require (
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.1
|
||||||
github.com/paulmach/go.geojson v1.4.0 // indirect
|
github.com/paulmach/go.geojson v1.4.0 // indirect
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.21.0
|
||||||
gitlab.scity.coop/maas/navitia-golang v0.0.0-20220429110621-5c22d6efdd0c
|
gitlab.scity.coop/maas/navitia-golang v0.0.0-20220429110621-5c22d6efdd0c
|
||||||
go.etcd.io/etcd/client/v3 v3.5.12
|
go.etcd.io/etcd/client/v3 v3.5.12
|
||||||
golang.org/x/image v0.5.0
|
golang.org/x/image v0.25.0
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
google.golang.org/grpc v1.75.1
|
google.golang.org/grpc v1.76.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.10
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.coopgo.io/coopgo-platform/agenda v1.0.0
|
git.coopgo.io/coopgo-platform/agenda v1.0.0
|
||||||
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20250925043614-b7ac71741f43
|
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20251008165122-38cb3c5ad9b4
|
||||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260
|
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260
|
||||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-20230310144446-feb935f8bf4e
|
git.coopgo.io/coopgo-platform/fleets v1.1.0
|
||||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20250616160304-0285c9494673
|
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858
|
||||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c
|
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c
|
||||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386
|
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386
|
||||||
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20250925053957-63fc3e7c834f
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251013140400-42fb40437ac3
|
||||||
git.coopgo.io/coopgo-platform/payments v0.0.0-20250616162131-77f7b00b8fc3
|
git.coopgo.io/coopgo-platform/payments v0.0.0-20251013175712-75d0288d2d4f
|
||||||
git.coopgo.io/coopgo-platform/routing-service v0.0.0-20250304234521-faabcc54f536
|
git.coopgo.io/coopgo-platform/routing-service v0.0.0-20250304234521-faabcc54f536
|
||||||
|
git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463
|
||||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af
|
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af
|
||||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20250910141021-fc5b33e88c2c
|
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251008070137-723c12a6573d
|
||||||
github.com/arran4/golang-ical v0.3.1
|
github.com/arran4/golang-ical v0.3.1
|
||||||
github.com/coreos/go-oidc/v3 v3.11.0
|
github.com/coreos/go-oidc/v3 v3.11.0
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1
|
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.1
|
||||||
github.com/minio/minio-go/v7 v7.0.43
|
github.com/minio/minio-go/v7 v7.0.43
|
||||||
github.com/paulmach/orb v0.11.1
|
github.com/paulmach/orb v0.12.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/stretchr/objx v0.5.2
|
github.com/stretchr/objx v0.5.3
|
||||||
github.com/xuri/excelize/v2 v2.7.1
|
github.com/xuri/excelize/v2 v2.9.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20250429082239-a95cd6eb5523 // indirect
|
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251008142525-4392f227836a // indirect
|
||||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||||
|
|
@ -93,27 +96,27 @@ require (
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gorilla/schema v1.4.1 // indirect
|
github.com/gorilla/schema v1.4.1 // indirect
|
||||||
github.com/mschoch/smat v0.2.0 // indirect
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
|
||||||
github.com/tidwall/geoindex v1.7.0 // indirect
|
github.com/tidwall/geoindex v1.7.0 // indirect
|
||||||
github.com/tidwall/rtree v1.10.0 // indirect
|
github.com/tidwall/rtree v1.10.0 // indirect
|
||||||
github.com/twpayne/go-polyline v1.1.1 // indirect
|
github.com/twpayne/go-polyline v1.1.1 // indirect
|
||||||
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||||
go.etcd.io/bbolt v1.4.0 // indirect
|
go.etcd.io/bbolt v1.4.0 // indirect
|
||||||
golang.org/x/mod v0.25.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/tools v0.33.0 // indirect
|
golang.org/x/mod v0.28.0 // indirect
|
||||||
|
golang.org/x/tools v0.37.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
ariga.io/atlas v0.32.0 // indirect
|
ariga.io/atlas v0.37.0 // indirect
|
||||||
git.coopgo.io/coopgo-platform/diags v0.0.0-20250212093351-64da61495c9d
|
github.com/AlexJarrah/go-ods v1.0.7 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/coreos/go-semver v0.3.0 // indirect
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||||
github.com/go-openapi/inflect v0.21.2 // indirect
|
github.com/go-openapi/inflect v0.21.3 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
|
@ -121,7 +124,7 @@ require (
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
|
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
|
|
@ -140,35 +143,36 @@ require (
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||||
github.com/twpayne/go-geom v1.5.7 // indirect
|
github.com/twpayne/go-geom v1.5.7 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
|
github.com/xuri/efp v0.0.1 // indirect
|
||||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
|
github.com/xuri/nfp v0.0.1 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
github.com/zclconf/go-cty v1.16.2 // indirect
|
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.12 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.12 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.17.3 // indirect
|
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.21.0 // indirect
|
go.uber.org/zap v1.21.0 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/sync v0.15.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|
|
||||||
164
go.sum
164
go.sum
|
|
@ -1,33 +1,43 @@
|
||||||
ariga.io/atlas v0.32.0 h1:y+77nueMrExLiKlz1CcPKh/nU7VSlWfBbwCShsJyvCw=
|
ariga.io/atlas v0.37.0 h1:MvbQ25CAHFslttEKEySwYNFrFUdLAPhtU1izOzjXV+o=
|
||||||
ariga.io/atlas v0.32.0/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
|
ariga.io/atlas v0.37.0/go.mod h1:mHE83ptCxEkd3rO3c7Rvkk6Djf6mVhEiSVhoiNu96CI=
|
||||||
git.coopgo.io/coopgo-platform/agenda v1.0.0 h1:rTHgva1JKKO0wAPlINegifMkHm+xOg3IWW4yQRy334w=
|
git.coopgo.io/coopgo-platform/agenda v1.0.0 h1:rTHgva1JKKO0wAPlINegifMkHm+xOg3IWW4yQRy334w=
|
||||||
git.coopgo.io/coopgo-platform/agenda v1.0.0/go.mod h1:/hToSla0p6SeWn1zo1MDrfxdmo7RBdZDkbLqCVituIM=
|
git.coopgo.io/coopgo-platform/agenda v1.0.0/go.mod h1:/hToSla0p6SeWn1zo1MDrfxdmo7RBdZDkbLqCVituIM=
|
||||||
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20250925043614-b7ac71741f43 h1:6gFe8+7tiAiUie7qCl4UaLv5QyUUtJvt23ju1Wa6Xg0=
|
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20251008165122-38cb3c5ad9b4 h1:L3HJnjQo5M0maOKkILAIq257tbHHCsr6OpAWGVQoGJs=
|
||||||
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20250925043614-b7ac71741f43/go.mod h1:J0vTvTd+NJ3dKcJ28Ca2xV7OMuglpJ9i4KPRb2SJqew=
|
git.coopgo.io/coopgo-platform/carpool-service v0.0.0-20251008165122-38cb3c5ad9b4/go.mod h1:4qy6Ha8/cA3L+hZyzPYbKJ0dFO+zyDh8W7bMXDXE6WQ=
|
||||||
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20250429082239-a95cd6eb5523 h1:hXoUOEZ+umiyR0SzYbGacJxiUbW4puw4phmkfTVfqPU=
|
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251008142525-4392f227836a h1:roBn3oQRQ/j5JBAP01wIs1UQvtm3tjui+D4EAa/Hfa4=
|
||||||
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20250429082239-a95cd6eb5523/go.mod h1:c9aJwNtY4PJuqAFYZ9afnx46UAZtWJ3P8ICZM02/DBA=
|
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20251008142525-4392f227836a/go.mod h1:c9aJwNtY4PJuqAFYZ9afnx46UAZtWJ3P8ICZM02/DBA=
|
||||||
git.coopgo.io/coopgo-platform/diags v0.0.0-20250212093351-64da61495c9d h1:fBxVvik4Cb/6d4+HAXZi9e8x8P9UBCJt8JcWgqnNjsE=
|
|
||||||
git.coopgo.io/coopgo-platform/diags v0.0.0-20250212093351-64da61495c9d/go.mod h1:diyq11WNkgJ0kYHdT7SphXPMQUyoq4lRk7T6IgM5yPA=
|
|
||||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260 h1:Li3dotY6raKu9+oxEgICU7nwdomYpjgu19i3mZNiqTc=
|
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260 h1:Li3dotY6raKu9+oxEgICU7nwdomYpjgu19i3mZNiqTc=
|
||||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260/go.mod h1:6cvvjv0RLSwBthIQ4TiuZoXFGvQXZ55hNSJchWXAgB4=
|
git.coopgo.io/coopgo-platform/emailing v0.0.0-20250212064257-167ef5864260/go.mod h1:6cvvjv0RLSwBthIQ4TiuZoXFGvQXZ55hNSJchWXAgB4=
|
||||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-20230310144446-feb935f8bf4e h1:eHahRTKlC8aBWYCd6LbXNcX8HoQhuZj31OFWrw0EL0U=
|
git.coopgo.io/coopgo-platform/fleets v1.1.0 h1:pfW/K3fWfap54yNfkLzBXjvOjjoTaEGFEqS/+VkHv7s=
|
||||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-20230310144446-feb935f8bf4e/go.mod h1:s9OIFCNcjBAbBzRNHwoCTYV6kAntPG9CpT3GVweGdTY=
|
git.coopgo.io/coopgo-platform/fleets v1.1.0/go.mod h1:nuK2mi1M2+DdntinqK/8C4ttW4WWyKCCY/xD1D7XjkE=
|
||||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20250616160304-0285c9494673 h1:cth7a8Mnx1C6C6F5rv7SoKVMHYpI/CioFubyi0xB+Dw=
|
git.coopgo.io/coopgo-platform/geography v0.0.0-20250616160304-0285c9494673 h1:cth7a8Mnx1C6C6F5rv7SoKVMHYpI/CioFubyi0xB+Dw=
|
||||||
git.coopgo.io/coopgo-platform/geography v0.0.0-20250616160304-0285c9494673/go.mod h1:TbR3g1Awa8hpAe6LR1z1EQbv2IBVgN5JQ/FjXfKX4K0=
|
git.coopgo.io/coopgo-platform/geography v0.0.0-20250616160304-0285c9494673/go.mod h1:TbR3g1Awa8hpAe6LR1z1EQbv2IBVgN5JQ/FjXfKX4K0=
|
||||||
|
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858 h1:4E0tbT8jj5oxaK66Ny61o7zqPaVc0qRN2cZG9IUR4Es=
|
||||||
|
git.coopgo.io/coopgo-platform/geography v0.0.0-20251010131258-ec939649e858/go.mod h1:TbR3g1Awa8hpAe6LR1z1EQbv2IBVgN5JQ/FjXfKX4K0=
|
||||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c h1:bY7PyrAgYY02f5IpDyf1WVfRqvWzivu31K6aEAYbWCw=
|
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c h1:bY7PyrAgYY02f5IpDyf1WVfRqvWzivu31K6aEAYbWCw=
|
||||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c/go.mod h1:lozSy6qlIIYhvKKXscZzz28HAtS0qBDUTv5nofLRmYA=
|
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20230310123255-5ef94ee0746c/go.mod h1:lozSy6qlIIYhvKKXscZzz28HAtS0qBDUTv5nofLRmYA=
|
||||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386 h1:v1JUdx8sknw2YYhFGz5cOAa1dEWNIBKvyiOpKr3RR+s=
|
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386 h1:v1JUdx8sknw2YYhFGz5cOAa1dEWNIBKvyiOpKr3RR+s=
|
||||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386/go.mod h1:1typNYtO+PQT6KG77vs/PUv0fO60/nbeSGZL2tt1LLg=
|
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20230329105908-a76c0412a386/go.mod h1:1typNYtO+PQT6KG77vs/PUv0fO60/nbeSGZL2tt1LLg=
|
||||||
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20250925053957-63fc3e7c834f h1:uePG+xhXdFiOhSyoNjJ3ZElwe/cWRm/++6iUHseY3Q8=
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251010131127-a2c82a1a5c8e h1:c0iCczcVxDbzbaQY04zzFpMXgHTRGcYOJ8LqYk9UYuo=
|
||||||
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20250925053957-63fc3e7c834f/go.mod h1:zDMfGVIvzuWV4Cw4bSi2kuEyMDbAfNfs7cGOAWNKfOo=
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251010131127-a2c82a1a5c8e/go.mod h1:npYccQZcZj1WzTHhpLfFHFSBA3RiZOkO5R9x4uy1a9I=
|
||||||
git.coopgo.io/coopgo-platform/payments v0.0.0-20250616162131-77f7b00b8fc3 h1:HGEo2E4IyprzshGKKeK7xMmorLNIGF8vbxa3zkBa+KM=
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251013125457-ab53f677d9cb h1:NotnSudZYn4cLAXJvtYor1XLkS5HXXNEPNgHy0Hw3Qs=
|
||||||
git.coopgo.io/coopgo-platform/payments v0.0.0-20250616162131-77f7b00b8fc3/go.mod h1:X2WqQN7ZLAucV9z1gPubkWChkHbmdOIxWdwn18DZ+YU=
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251013125457-ab53f677d9cb/go.mod h1:npYccQZcZj1WzTHhpLfFHFSBA3RiZOkO5R9x4uy1a9I=
|
||||||
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251013140400-42fb40437ac3 h1:jo7fF7tLIAU110tUSIYXkMAvu30g8wHzZCLq3YomooQ=
|
||||||
|
git.coopgo.io/coopgo-platform/multimodal-routing v0.0.0-20251013140400-42fb40437ac3/go.mod h1:npYccQZcZj1WzTHhpLfFHFSBA3RiZOkO5R9x4uy1a9I=
|
||||||
|
git.coopgo.io/coopgo-platform/payments v0.0.0-20251008125601-e36cd6e557da h1:5D2B1WkRolbXFUHsqPHnYtTeweSZ+iCHlx6S2KKxmxQ=
|
||||||
|
git.coopgo.io/coopgo-platform/payments v0.0.0-20251008125601-e36cd6e557da/go.mod h1:gSAH2Tr9x8K8QC0vsUMwSWLrQOlsG+v64ACrjYw4BL0=
|
||||||
|
git.coopgo.io/coopgo-platform/payments v0.0.0-20251013175712-75d0288d2d4f h1:B/+AP+rLFx8AojO2bKV3R93kMU84g8Dhy7DNVoT8xCY=
|
||||||
|
git.coopgo.io/coopgo-platform/payments v0.0.0-20251013175712-75d0288d2d4f/go.mod h1:gSAH2Tr9x8K8QC0vsUMwSWLrQOlsG+v64ACrjYw4BL0=
|
||||||
git.coopgo.io/coopgo-platform/routing-service v0.0.0-20250304234521-faabcc54f536 h1:SllXX1VJXulfhNi+Pd0R9chksm8zO6gkWcTQ/uSMsdc=
|
git.coopgo.io/coopgo-platform/routing-service v0.0.0-20250304234521-faabcc54f536 h1:SllXX1VJXulfhNi+Pd0R9chksm8zO6gkWcTQ/uSMsdc=
|
||||||
git.coopgo.io/coopgo-platform/routing-service v0.0.0-20250304234521-faabcc54f536/go.mod h1:Nh7o15LlV0OuO9zxvJIs9FlelpeAaLYkXtFdgIkFrgg=
|
git.coopgo.io/coopgo-platform/routing-service v0.0.0-20250304234521-faabcc54f536/go.mod h1:Nh7o15LlV0OuO9zxvJIs9FlelpeAaLYkXtFdgIkFrgg=
|
||||||
|
git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463 h1:TjjqFEkRLDqy300pGOLIhsVYBpE0J640pFW9//OknzA=
|
||||||
|
git.coopgo.io/coopgo-platform/saved-search v0.0.0-20251008070953-efccea3f6463/go.mod h1:0fuGuYub5CBy9NB6YMqxawE0HoBaxPb9gmSw1gjfDy0=
|
||||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af h1:KxHim1dFcOVbFhRqelec8cJ65QBD2cma6eytW8llgYY=
|
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af h1:KxHim1dFcOVbFhRqelec8cJ65QBD2cma6eytW8llgYY=
|
||||||
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af/go.mod h1:mad9D+WICDdpJzB+8H/wEVVbllK2mU6VLVByrppc9x0=
|
git.coopgo.io/coopgo-platform/sms v0.0.0-20250523074631-1f1e7fc6b7af/go.mod h1:mad9D+WICDdpJzB+8H/wEVVbllK2mU6VLVByrppc9x0=
|
||||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20250910141021-fc5b33e88c2c h1:U7EgF5Asj6a65nNXo6m4luR27CL5nDfxhY6GFWX46uU=
|
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251008070137-723c12a6573d h1:eBzRP50PXlXlLhgZjFhjTuoxIuQ3N/+5A6RIZyZEMAs=
|
||||||
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20250910141021-fc5b33e88c2c/go.mod h1:jnSYIECcH/nL8bLBwYD2WmJSPgWUC2rioOXxivWBTBM=
|
git.coopgo.io/coopgo-platform/solidarity-transport v0.0.0-20251008070137-723c12a6573d/go.mod h1:iaFXcIn7DYtKlLrSYs9C47Dt7eeMGYkpx+unLCx8TpQ=
|
||||||
|
github.com/AlexJarrah/go-ods v1.0.7 h1:QxhYKncbsgf59BNNOcc4XB7wxKvOrSwtC0fpf6/gtsM=
|
||||||
|
github.com/AlexJarrah/go-ods v1.0.7/go.mod h1:tifLS6QTLIRhFV4zSjZ59700fZOGeqqQD8KBBOb/F3w=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.3.2/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.3.2/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
|
|
@ -120,14 +130,14 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
|
github.com/go-openapi/inflect v0.21.3 h1:TmQvw+9eLrsNp4X0BBQacEZZtAnzk2z1FaLdQQJsDiU=
|
||||||
github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
github.com/go-openapi/inflect v0.21.3/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
|
@ -138,8 +148,8 @@ github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+j
|
||||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
|
@ -170,8 +180,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
|
||||||
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
|
@ -242,8 +252,8 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P
|
||||||
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
|
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
|
||||||
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
|
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
|
||||||
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=
|
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=
|
||||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
github.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s=
|
||||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
github.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
|
@ -258,36 +268,36 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
|
||||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
||||||
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||||
|
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
|
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
|
||||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
|
@ -296,8 +306,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
|
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
|
||||||
|
|
@ -309,6 +319,8 @@ github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UM
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tidwall/rtree v1.10.0 h1:+EcI8fboEaW1L3/9oW/6AMoQ8HiEIHyR7bQOGnmz4Mg=
|
github.com/tidwall/rtree v1.10.0 h1:+EcI8fboEaW1L3/9oW/6AMoQ8HiEIHyR7bQOGnmz4Mg=
|
||||||
github.com/tidwall/rtree v1.10.0/go.mod h1:iDJQ9NBRtbfKkzZu02za+mIlaP+bjYPnunbSNidpbCQ=
|
github.com/tidwall/rtree v1.10.0/go.mod h1:iDJQ9NBRtbfKkzZu02za+mIlaP+bjYPnunbSNidpbCQ=
|
||||||
|
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||||
|
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||||
github.com/twpayne/go-geom v1.2.1/go.mod h1:90yvs0wf/gyT5eQ9W4v5WOZ9w/Xnrj5RMlA9XNKqxyA=
|
github.com/twpayne/go-geom v1.2.1/go.mod h1:90yvs0wf/gyT5eQ9W4v5WOZ9w/Xnrj5RMlA9XNKqxyA=
|
||||||
github.com/twpayne/go-geom v1.5.7 h1:7fdceDUr03/MP7rAKOaTV6x9njMiQdxB/D0PDzMTCDc=
|
github.com/twpayne/go-geom v1.5.7 h1:7fdceDUr03/MP7rAKOaTV6x9njMiQdxB/D0PDzMTCDc=
|
||||||
github.com/twpayne/go-geom v1.5.7/go.mod h1:y4fTAQtLedXW8eG2Yo4tYrIGN1yIwwKkmA+K3iSHKBA=
|
github.com/twpayne/go-geom v1.5.7/go.mod h1:y4fTAQtLedXW8eG2Yo4tYrIGN1yIwwKkmA+K3iSHKBA=
|
||||||
|
|
@ -326,10 +338,16 @@ github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6
|
||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
|
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
|
||||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
|
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||||
|
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
|
github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
|
||||||
github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
|
github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
|
||||||
|
github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw=
|
||||||
|
github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s=
|
||||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
||||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
|
github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q=
|
||||||
|
github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
|
|
@ -337,8 +355,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
|
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
|
||||||
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||||
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
|
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
|
||||||
|
|
@ -354,8 +372,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+
|
||||||
go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg=
|
go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw=
|
go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw=
|
||||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
|
|
@ -378,6 +396,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
|
@ -385,18 +405,22 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
|
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||||
|
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
|
@ -408,8 +432,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
@ -418,8 +444,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -440,8 +466,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
|
@ -454,8 +480,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|
@ -464,24 +492,24 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff h1:A90eA31Wq6HOMIQlLfzFwzqGKBTuaVztYu/g8sn+8Zc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type APIHandler struct {
|
|
||||||
idp *identification.IdentificationProvider
|
|
||||||
config *viper.Viper
|
|
||||||
services *services.ServicesHandler
|
|
||||||
cache cache.CacheHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAPIHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler) (*APIHandler, error) {
|
|
||||||
return &APIHandler{
|
|
||||||
idp: idp,
|
|
||||||
config: cfg,
|
|
||||||
services: svc,
|
|
||||||
cache: cache,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *APIHandler) NotFound(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h APIHandler) GetCache(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
cacheid := vars["cacheid"]
|
|
||||||
// Use a channel to synchronize the goroutines
|
|
||||||
ch := make(chan []byte)
|
|
||||||
// Fetch data from cache asynchronously
|
|
||||||
go func() {
|
|
||||||
d, err := h.cache.Get(cacheid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
ch <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var data []any
|
|
||||||
if val, ok := d.([]any); ok {
|
|
||||||
data = val
|
|
||||||
} else {
|
|
||||||
data = []any{d}
|
|
||||||
}
|
|
||||||
j := toJSON(data, w, r)
|
|
||||||
ch <- j // Signal that the data has been fetched successfully
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
// wait for the JSON marshaling goroutine to finish
|
|
||||||
j := <-ch
|
|
||||||
if j == nil {
|
|
||||||
return // Stop processing if an error occurred
|
|
||||||
}
|
|
||||||
// Send the JSON response to the client
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(j)
|
|
||||||
|
|
||||||
<-ch
|
|
||||||
}
|
|
||||||
func toJSON(data []any, w http.ResponseWriter, r *http.Request) []byte {
|
|
||||||
result := data
|
|
||||||
if limitsmin, ok := r.URL.Query()["limits.min"]; ok {
|
|
||||||
min, _ := strconv.Atoi(limitsmin[0])
|
|
||||||
if limitsmax, ok := r.URL.Query()["limits.max"]; ok {
|
|
||||||
max, _ := strconv.Atoi(limitsmax[0])
|
|
||||||
if max > len(data) {
|
|
||||||
result = data[min:]
|
|
||||||
} else {
|
|
||||||
result = data[min:max]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = data[min:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j, err := json.Marshal(result)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return j
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
|
||||||
ics "github.com/arran4/golang-ical"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *APIHandler) icsCalendar(events []services.AgendaEvent) (*ics.Calendar, error) {
|
|
||||||
calendar := ics.NewCalendarFor(h.config.GetString("service_name"))
|
|
||||||
|
|
||||||
for _, e := range events {
|
|
||||||
vevent := ics.NewEvent(e.ID)
|
|
||||||
vevent.SetSummary(e.Name)
|
|
||||||
vevent.SetDescription(e.Description)
|
|
||||||
if e.Allday {
|
|
||||||
vevent.SetAllDayStartAt(e.Startdate)
|
|
||||||
if e.Enddate.After(e.Startdate) {
|
|
||||||
vevent.SetAllDayEndAt(e.Enddate.Add(24 * time.Hour))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timeloc, err := time.LoadLocation("Europe/Paris")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Tried to load timezone location Europe/Paris. Error. Missing zones in container ?")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
starttime, err := time.ParseInLocation("15:04", e.Starttime, timeloc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
startdatetime := time.Date(e.Startdate.Year(), e.Startdate.Month(), e.Startdate.Day(), starttime.Hour(), starttime.Minute(), 0, 0, timeloc)
|
|
||||||
|
|
||||||
endtime, err := time.Parse("15:04", e.Endtime)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enddatetime := time.Date(e.Enddate.Year(), e.Enddate.Month(), e.Enddate.Day(), endtime.Hour(), endtime.Minute(), 0, 0, timeloc)
|
|
||||||
|
|
||||||
vevent.SetStartAt(startdatetime)
|
|
||||||
vevent.SetEndAt(enddatetime)
|
|
||||||
}
|
|
||||||
calendar.AddVEvent(vevent)
|
|
||||||
}
|
|
||||||
return calendar, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *APIHandler) CalendarGlobal(w http.ResponseWriter, r *http.Request) {
|
|
||||||
enabled := h.config.GetBool("modules.agenda.enabled") && h.config.GetBool("modules.agenda.calendars.global.enabled")
|
|
||||||
if !enabled {
|
|
||||||
log.Error().Msg("global calendar not activated in configuration")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
events, err := h.services.GetAgendaEvents()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error retrieving agenda events")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
calendar, err := h.icsCalendar(events)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error while creating ics calendar")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "text/calendar")
|
|
||||||
w.Write([]byte(calendar.Serialize()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *APIHandler) CalendarOrganizations(w http.ResponseWriter, r *http.Request) {
|
|
||||||
enabled := h.config.GetBool("modules.agenda.enabled") && h.config.GetBool("modules.agenda.calendars.organizations.enabled")
|
|
||||||
if !enabled {
|
|
||||||
log.Error().Msg("organizations calendar not activated in configuration")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO set additional calendar rights in group configuration to prevent default behavior ?
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
|
|
||||||
events, err := h.services.GetAgendaEvents()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error retrieving agenda events")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredEvents := []services.AgendaEvent{}
|
|
||||||
|
|
||||||
for _, e := range events {
|
|
||||||
for _, g := range e.Owners {
|
|
||||||
if g == groupid {
|
|
||||||
filteredEvents = append(filteredEvents, e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
calendar, err := h.icsCalendar(filteredEvents)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error while creating ics calendar")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "text/calendar")
|
|
||||||
w.Write([]byte(calendar.Serialize()))
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FlatMaps []map[string]any
|
|
||||||
|
|
||||||
func (maps FlatMaps) GetHeaders() (res []string) {
|
|
||||||
keys := map[string]bool{}
|
|
||||||
for _, m := range maps {
|
|
||||||
for k, _ := range m {
|
|
||||||
if _, ok := keys[k]; !ok {
|
|
||||||
keys[k] = true
|
|
||||||
res = append(res, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (maps FlatMaps) GetValues() (res [][]string) {
|
|
||||||
headers := maps.GetHeaders()
|
|
||||||
for _, m := range maps {
|
|
||||||
line := []string{}
|
|
||||||
for _, k := range headers {
|
|
||||||
if v, ok := m[k]; ok && v != nil {
|
|
||||||
line = append(line, fmt.Sprint(v))
|
|
||||||
} else {
|
|
||||||
line = append(line, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res = append(res, line)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h APIHandler) CacheExport(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
cacheid := vars["cacheid"]
|
|
||||||
|
|
||||||
d, err := h.cache.Get(cacheid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Error getting cache")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if data, ok := d.([]any); ok {
|
|
||||||
|
|
||||||
flatmaps := FlatMaps{}
|
|
||||||
|
|
||||||
for _, v := range data {
|
|
||||||
fm := map[string]any{}
|
|
||||||
flatten("", v.(map[string]any), fm)
|
|
||||||
flatmaps = append(flatmaps, fm)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/csv")
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=export-%s.csv", cacheid))
|
|
||||||
c := csv.NewWriter(w)
|
|
||||||
c.Write(flatmaps.GetHeaders())
|
|
||||||
c.WriteAll(flatmaps.GetValues())
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func flatten(prefix string, src map[string]any, dest map[string]any) {
|
|
||||||
if len(prefix) > 0 {
|
|
||||||
prefix += "."
|
|
||||||
}
|
|
||||||
for k, v := range src {
|
|
||||||
switch child := v.(type) {
|
|
||||||
case map[string]any:
|
|
||||||
flatten(prefix+k, child, dest)
|
|
||||||
case []any:
|
|
||||||
for i := 0; i < len(child); i++ {
|
|
||||||
dest[prefix+k+"."+strconv.Itoa(i)] = child[i]
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// log.Trace().Str("key", prefix+k).Any("value", v).Msg("")
|
|
||||||
dest[prefix+k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *APIHandler) GeoAutocomplete(w http.ResponseWriter, r *http.Request) {
|
|
||||||
pelias := h.config.GetString("geo.pelias.url")
|
|
||||||
|
|
||||||
t, ok := r.URL.Query()["text"]
|
|
||||||
|
|
||||||
if !ok || len(t[0]) < 1 {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
text := t[0]
|
|
||||||
|
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/autocomplete?text=%s", pelias, text))
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var response map[string]any
|
|
||||||
jsonErr := json.Unmarshal(body, &response)
|
|
||||||
if jsonErr != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
j, err := json.Marshal(response["features"])
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h APIHandler) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|
||||||
oauth2Token, err := h.idp.OAuth2Config.Exchange(context.Background(), r.URL.Query().Get("code"))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Exchange error")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the ID Token from OAuth2 token.
|
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
|
||||||
if !ok {
|
|
||||||
log.Error().Msg("Cannot retrieve ID token")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.idp.TokenVerifier.Verify(context.Background(), rawIDToken)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Not able to verify token")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
session, _ := h.idp.SessionsStore.Get(r, "parcoursmob_session")
|
|
||||||
session.Values["idtoken"] = rawIDToken
|
|
||||||
|
|
||||||
redirect := "/app/"
|
|
||||||
|
|
||||||
if session.Values["redirect"] != nil && session.Values["redirect"] != "" {
|
|
||||||
redirect = session.Values["redirect"].(string)
|
|
||||||
delete(session.Values, "redirect")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = session.Save(r, w); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot save session")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, redirect, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package protected
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProtectedAPIHandler struct {
|
|
||||||
ApiKey string
|
|
||||||
idp *identification.IdentificationProvider
|
|
||||||
config *viper.Viper
|
|
||||||
services *services.ServicesHandler
|
|
||||||
cache cache.CacheHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProtectedAPIHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler) (*ProtectedAPIHandler, error) {
|
|
||||||
return &ProtectedAPIHandler{
|
|
||||||
ApiKey: cfg.GetString("services.api.api_key"),
|
|
||||||
idp: idp,
|
|
||||||
config: cfg,
|
|
||||||
services: svc,
|
|
||||||
cache: cache,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ProtectedAPIHandler) NotFound(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package protected
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
groupsgrpc "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ProtectedAPIHandler) Users(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
h.postUsers(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ProtectedAPIHandler) postUsers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var user storage.Account
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
|
||||||
err := decoder.Decode(&user)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not read account input")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := grpcapi.AccountFromStorageType(&user)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not generate protobuf for account")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.Register(context.Background(), &grpcapi.RegisterRequest{
|
|
||||||
Account: account,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("grpc request issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if g, ok := user.Metadata["import_in_group"]; ok {
|
|
||||||
if group, ok := g.(string); ok {
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.Subscribe(context.Background(), &groupsgrpc.SubscribeRequest{
|
|
||||||
Groupid: group,
|
|
||||||
Memberid: resp.Account.Id,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("grpc request issue, groups")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
@ -1,593 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
|
||||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) Administration(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var (
|
|
||||||
wg sync.WaitGroup
|
|
||||||
accounts, beneficiaries []mobilityaccountsstorage.Account
|
|
||||||
bookings []fleetsstorage.Booking
|
|
||||||
accountsErr, beneficiariesErr, bookingsErr, groupsResponseErr, eventsResponseErr, groupsBatchErr error
|
|
||||||
groups = []groupstorage.Group{}
|
|
||||||
responses = []agendastorage.Event{}
|
|
||||||
groupsResponse *groupsmanagement.GetGroupsResponse
|
|
||||||
eventsResponse *agenda.GetEventsResponse
|
|
||||||
groupids = []string{}
|
|
||||||
groupsBatchResponse *groupsmanagement.GetGroupsBatchResponse
|
|
||||||
)
|
|
||||||
// Retrieve accounts in a goroutine
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
accounts, accountsErr = h.services.GetAccounts()
|
|
||||||
}()
|
|
||||||
// Retrieve beneficiaries in a goroutine
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
beneficiaries, beneficiariesErr = h.services.GetBeneficiaries()
|
|
||||||
}()
|
|
||||||
// Retrieve bookings in a goroutine
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
bookings, bookingsErr = h.services.GetBookings()
|
|
||||||
}()
|
|
||||||
// Retrieve groupsRequest in a goroutine
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
request := &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_organizations"},
|
|
||||||
}
|
|
||||||
groupsResponse, groupsResponseErr = h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), request)
|
|
||||||
for _, group := range groupsResponse.Groups {
|
|
||||||
g := group.ToStorageType()
|
|
||||||
groups = append(groups, g)
|
|
||||||
}
|
|
||||||
sort.Sort(sorting.GroupsByName(groups))
|
|
||||||
}()
|
|
||||||
// Retrieve Events in a goroutine
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
eventsResponse, eventsResponseErr = h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
})
|
|
||||||
for _, e := range eventsResponse.Events {
|
|
||||||
groupids = append(groupids, e.Owners...)
|
|
||||||
responses = append(responses, e.ToStorageType())
|
|
||||||
}
|
|
||||||
sort.Sort(sorting.EventsByStartdate(responses))
|
|
||||||
}()
|
|
||||||
wg.Add(1)
|
|
||||||
// Retrieve groupsBatch in a goroutine
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
groupsBatchResponse, groupsBatchErr = h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groupps := map[string]any{}
|
|
||||||
if groupsBatchErr == nil {
|
|
||||||
for _, g := range groupsBatchResponse.Groups {
|
|
||||||
groupps[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
if accountsErr != nil || beneficiariesErr != nil || bookingsErr != nil || groupsResponseErr != nil || eventsResponseErr != nil {
|
|
||||||
log.Error().
|
|
||||||
Any("accounts error", accountsErr).
|
|
||||||
Any("beneficiaries error", beneficiariesErr).
|
|
||||||
Any("bookings error", bookingsErr).
|
|
||||||
Any("groups response error", groupsResponseErr).
|
|
||||||
Any("events response error", eventsResponseErr).
|
|
||||||
Any("groups batch error", groupsBatchErr).
|
|
||||||
Msg("Error in retrieving administration data")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.Administration(w, r, accounts, beneficiaries, groups, bookings, responses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AdministrationCreateGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
if r.FormValue("name") == "" {
|
|
||||||
|
|
||||||
log.Error().Str("name", r.FormValue("name")).Msg("Invalid name")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
|
|
||||||
groupid := uuid.NewString()
|
|
||||||
|
|
||||||
dataMap := map[string]any{
|
|
||||||
"name": r.FormValue("name"),
|
|
||||||
"modules": modules,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot create PB struct from data map")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request_organization := &groupsmanagement.AddGroupRequest{
|
|
||||||
Group: &groupsmanagement.Group{
|
|
||||||
Id: groupid,
|
|
||||||
Namespace: "parcoursmob_organizations",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
request_role := &groupsmanagement.AddGroupRequest{
|
|
||||||
Group: &groupsmanagement.Group{
|
|
||||||
Id: groupid + ":admin",
|
|
||||||
Namespace: "parcoursmob_roles",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.AddGroup(context.TODO(), request_organization)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - AddGroup")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// Create the admin role for the organization
|
|
||||||
go func() {
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.AddGroup(context.TODO(), request_role)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - AddGroup")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/administration/groups/%s", groupid), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.AdministrationCreateGroup(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AdministrationGroupDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
|
|
||||||
request := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: groupid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - GetGroup")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groupmembers, admins, err := h.groupmembers(groupid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("inssue retrieving group members")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.AdministrationGroupDisplay(w, r, resp.Group.ToStorageType(), groupmembers, admins)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AdministrationGroupInviteAdmin(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
var (
|
|
||||||
groupresp *groupsmanagement.GetGroupResponse
|
|
||||||
accountresp *accounts.GetAccountUsernameResponse
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
groupresp, err = h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: groupid,
|
|
||||||
Namespace: "parcoursmob_organizations",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - GetGroup")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
accountresp, err = h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &accounts.GetAccountUsernameRequest{
|
|
||||||
Username: r.FormValue("username"),
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
log.Print("account exists")
|
|
||||||
// Account already exists : adding the existing account to admin list
|
|
||||||
account := accountresp.Account.ToStorageType()
|
|
||||||
// account.Data["groups"] = append(account.Data["groups"].([]any), groupid, groupid)
|
|
||||||
account.Data["groups"] = append(account.Data["groups"].([]any), groupid, groupid+":admin")
|
|
||||||
|
|
||||||
as, _ := accounts.AccountFromStorageType(&account)
|
|
||||||
|
|
||||||
if _, err = h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), &accounts.UpdateDataRequest{Account: as}); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not set groups to user account")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"group": groupresp.Group.ToStorageType().Data["name"],
|
|
||||||
"baseUrl": h.config.GetString("base_url"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("onboarding.existing_administrator", r.FormValue("username"), data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot send email")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Onboard now administrator
|
|
||||||
onboarding := map[string]any{
|
|
||||||
"username": r.FormValue("username"),
|
|
||||||
"group": groupid,
|
|
||||||
"admin": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, 16)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue creating random bytes")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key := base64.RawURLEncoding.EncodeToString(b)
|
|
||||||
|
|
||||||
if err := h.cache.PutWithTTL("onboarding/"+key, onboarding, 168*time.Hour); err != nil { // 1 week TTL
|
|
||||||
fmt.Println(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"group": groupresp.Group.ToStorageType().Data["name"],
|
|
||||||
"key": key,
|
|
||||||
"baseUrl": h.config.GetString("base_url"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("onboarding.new_administrator", r.FormValue("username"), data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot send email")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/administration/groups/%s", groupid), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AdministrationGroupInviteMember(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
group, err := h.services.GetGroup(groupid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - GetGroup")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.ParseForm()
|
|
||||||
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &accounts.GetAccountUsernameRequest{
|
|
||||||
Username: r.FormValue("username"),
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
account := accountresp.Account.ToStorageType()
|
|
||||||
account.Data["groups"] = append(account.Data["groups"].([]any), group.ID)
|
|
||||||
|
|
||||||
as, _ := accounts.AccountFromStorageType(&account)
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.MobilityAccounts.UpdateData(
|
|
||||||
context.TODO(),
|
|
||||||
&accounts.UpdateDataRequest{
|
|
||||||
Account: as,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
data := map[string]any{
|
|
||||||
"group": group.Data["name"],
|
|
||||||
"baseUrl": h.config.GetString("base_url"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("onboarding.existing_member", r.FormValue("username"), data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("error sending email onboarding.existing_member")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Onboard new administrator
|
|
||||||
onboarding := map[string]any{
|
|
||||||
"username": r.FormValue("username"),
|
|
||||||
"group": group.ID,
|
|
||||||
"admin": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, 16)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key := base64.RawURLEncoding.EncodeToString(b)
|
|
||||||
|
|
||||||
h.cache.PutWithTTL("onboarding/"+key, onboarding, 168*time.Hour) // 1 week TTL
|
|
||||||
data := map[string]any{
|
|
||||||
"group": group.Data["name"],
|
|
||||||
"key": key,
|
|
||||||
"baseUrl": h.config.GetString("base_url"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("onboarding.new_member", r.FormValue("username"), data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("error sending email onboarding.new_member")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/app/administration/groups/"+group.ID, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func filteVehicle(r *http.Request, v *fleets.Vehicle) bool {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
for _, n := range v.Administrators {
|
|
||||||
if n == group.ID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) AdminStatVehicles(w http.ResponseWriter, r *http.Request) {
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
administrators := []string{}
|
|
||||||
reequest := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
reesp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), reequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Fleets service - GetVehicles")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles := []fleetsstorage.Vehicle{}
|
|
||||||
for _, vehiicle := range reesp.Vehicles {
|
|
||||||
|
|
||||||
v := vehiicle.ToStorageType()
|
|
||||||
adminfound := false
|
|
||||||
for _, a := range administrators {
|
|
||||||
if a == v.Administrators[0] {
|
|
||||||
adminfound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !adminfound {
|
|
||||||
administrators = append(administrators, v.Administrators[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicleBookings := []fleetsstorage.Booking{}
|
|
||||||
for _, b := range v.Bookings {
|
|
||||||
if b.Unavailableto.After(time.Now()) {
|
|
||||||
vehicleBookings = append(vehicleBookings, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Bookings = vehicleBookings
|
|
||||||
|
|
||||||
vehicles = append(vehicles, v)
|
|
||||||
|
|
||||||
}
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
if len(administrators) > 0 {
|
|
||||||
admingroups, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: administrators,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - GetGroupsBatch")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range admingroups.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
sort.Sort(sorting.VehiclesByLicencePlate(vehicles))
|
|
||||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
||||||
h.Renderer.AdminStatVehicles(w, r, vehicles, bookings, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) AdminStatBookings(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vehicles := map[string]fleetsstorage.Vehicle{}
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
|
|
||||||
reequest := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
reesp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), reequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Fleets service - GetVehicles")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_ids := []string{}
|
|
||||||
|
|
||||||
for _, vehicle := range reesp.Vehicles {
|
|
||||||
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
|
|
||||||
for _, b := range v.Bookings {
|
|
||||||
bookings = append(bookings, b)
|
|
||||||
beneficiaries_ids = append(beneficiaries_ids, b.Driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles[v.ID] = v
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
admingroups, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_organizations"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Groups management service - GetGroups")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range admingroups.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: beneficiaries_ids,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Mobility accounts service - GetAccountsBatch")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_map := map[string]any{}
|
|
||||||
for _, ben := range beneficiaries.Accounts {
|
|
||||||
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
||||||
h.Renderer.AdminStatBookings(w, r, vehicles, bookings, groups, beneficiaries_map)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) members() ([]*accounts.Account, error) {
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(context.TODO(), &accounts.GetAccountsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Accounts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) groupmembers(groupid string) (groupmembers []mobilityaccountsstorage.Account, admins []mobilityaccountsstorage.Account, err error) {
|
|
||||||
members, err := h.members()
|
|
||||||
if err != nil {
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot get members")
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
groupmembers = []mobilityaccountsstorage.Account{}
|
|
||||||
admins = []mobilityaccountsstorage.Account{}
|
|
||||||
|
|
||||||
for _, m := range members {
|
|
||||||
mm := m.ToStorageType()
|
|
||||||
for _, g := range mm.Data["groups"].([]any) {
|
|
||||||
if g.(string) == groupid {
|
|
||||||
groupmembers = append(groupmembers, mm)
|
|
||||||
}
|
|
||||||
if g.(string) == groupid+":admin" {
|
|
||||||
admins = append(admins, mm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupmembers, admins, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) AdminStatBeneficaires(w http.ResponseWriter, r *http.Request) {
|
|
||||||
beneficiaries, err := h.services.GetBeneficiaries()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Cannot get beneficiaries")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
h.cache.PutWithTTL(cacheid, beneficiaries, 1*time.Hour)
|
|
||||||
h.Renderer.AdminStatBeneficaires(w, r, beneficiaries, cacheid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) AdminStatEvents(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue in Agenda service - GetEvents")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responses := []agendastorage.Event{}
|
|
||||||
|
|
||||||
groupids := []string{}
|
|
||||||
for _, e := range resp.Events {
|
|
||||||
groupids = append(groupids, e.Owners...)
|
|
||||||
responses = append(responses, e.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.EventsByStartdate(responses))
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, g := range groupsresp.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.Renderer.AdminStatEvents(w, r, responses, groups)
|
|
||||||
}
|
|
||||||
|
|
@ -1,790 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
|
||||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 (h *ApplicationHandler) AgendaHome(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
Mindate: timestamppb.New(time.Now().Add(-24 * time.Hour)),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responses := []agendastorage.Event{}
|
|
||||||
|
|
||||||
groupids := []string{}
|
|
||||||
for _, e := range resp.Events {
|
|
||||||
groupids = append(groupids, e.Owners...)
|
|
||||||
responses = append(responses, e.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.EventsByStartdate(responses))
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, g := range groupsresp.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.Renderer.AgendaHome(w, r, responses, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AgendaHistory(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
//Maxdate: timestamppb.New(time.Now().Add(24 * time.Hour)),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responses := []agendastorage.Event{}
|
|
||||||
|
|
||||||
groupids := []string{}
|
|
||||||
for _, e := range resp.Events {
|
|
||||||
groupids = append(groupids, e.Owners...)
|
|
||||||
responses = append(responses, e.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.EventsByStartdate(responses))
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, g := range groupsresp.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.Renderer.AgendaHistory(w, r, responses, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AgendaCreateEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
// Get current group
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
eventForm, err := parseEventsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Any("eventFrom", eventForm).Msg("Form data submitted to create event")
|
|
||||||
|
|
||||||
data, _ := structpb.NewStruct(map[string]any{
|
|
||||||
"address": eventForm.Address,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &agenda.CreateEventRequest{
|
|
||||||
Event: &agenda.Event{
|
|
||||||
Namespace: "parcoursmob_dispositifs",
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
Type: eventForm.Type,
|
|
||||||
Name: eventForm.Name,
|
|
||||||
Description: eventForm.Description,
|
|
||||||
Startdate: timestamppb.New(*eventForm.Startdate),
|
|
||||||
Enddate: timestamppb.New(*eventForm.Enddate),
|
|
||||||
Starttime: eventForm.Starttime,
|
|
||||||
Endtime: eventForm.Endtime,
|
|
||||||
Allday: eventForm.Allday,
|
|
||||||
MaxSubscribers: int64(eventForm.MaxSubscribers),
|
|
||||||
Data: data,
|
|
||||||
Deleted: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.CreateEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, header, err := r.FormFile("file-upload")
|
|
||||||
if err == nil {
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
document_type := r.FormValue("file_type")
|
|
||||||
document_name := r.FormValue("file_name")
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"file_type": document_type,
|
|
||||||
"file_name": document_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s_%s", resp.Event.Id, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Error uploading file")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err != http.ErrMissingFile {
|
|
||||||
log.Error().Err(err).Msg("Error retrieving file")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", resp.Event.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.AgendaCreateEvent(w, r, h.config.GetStringSlice("modules.agenda.documents_types"), h.config.GetStringMapString("storage.files.file_types"), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AgendaDisplayEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventid := vars["eventid"]
|
|
||||||
|
|
||||||
request := &agenda.GetEventRequest{
|
|
||||||
Id: eventid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
grouprequest := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: resp.Event.Owners[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), grouprequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribers := map[string]any{}
|
|
||||||
|
|
||||||
accids := []string{}
|
|
||||||
for _, v := range resp.Event.Subscriptions {
|
|
||||||
accids = append(accids, v.Subscriber)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriberresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
|
||||||
context.TODO(),
|
|
||||||
&mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: accids,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, sub := range subscriberresp.Accounts {
|
|
||||||
subscribers[sub.Id] = sub.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
accountids := []string{}
|
|
||||||
for _, m := range group.Members {
|
|
||||||
if !contains(resp.Event.Subscriptions, m) {
|
|
||||||
accountids = append(accountids, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
|
||||||
context.TODO(),
|
|
||||||
&mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: accountids,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
accounts := []any{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, acc := range accountresp.Accounts {
|
|
||||||
accounts = append(accounts, acc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_AGENDA + "/" + eventid)
|
|
||||||
|
|
||||||
events_file_types := h.config.GetStringSlice("modules.agenda.documents_types")
|
|
||||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
|
||||||
|
|
||||||
h.Renderer.AgendaDisplayEvent(w, r, resp.Event.ToStorageType(), groupresp.Group.ToStorageType(), events_file_types, file_types_map, documents, subscribers, accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AgendaSubscribeEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
current_group, err := h.currentGroup(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current_user_token, current_user_claims, err := h.currentUser(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventid := vars["eventid"]
|
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subscriber := r.FormValue("subscriber")
|
|
||||||
data := 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"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
datapb, err := structpb.NewStruct(data)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &agenda.SubscribeEventRequest{
|
|
||||||
Eventid: eventid,
|
|
||||||
Subscriber: subscriber,
|
|
||||||
Data: datapb,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.Agenda.SubscribeEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventid), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 contains(s []*agenda.Subscription, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if a.Subscriber == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////Update Event/////////////////////////////////////////
|
|
||||||
func (h *ApplicationHandler) AgendaUpdateEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
adm := strings.Split(r.URL.Path, "/")
|
|
||||||
eventID := adm[3]
|
|
||||||
request := &agenda.GetEventRequest{
|
|
||||||
Id: eventID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method == "POST" {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
eventForm, err := parseEventsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := structpb.NewStruct(map[string]any{
|
|
||||||
"address": eventForm.Address,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &agenda.UpdateEventRequest{
|
|
||||||
Event: &agenda.Event{
|
|
||||||
Namespace: "parcoursmob_dispositifs",
|
|
||||||
Id: eventID,
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
Type: eventForm.Type,
|
|
||||||
Name: eventForm.Name,
|
|
||||||
Description: eventForm.Description,
|
|
||||||
Startdate: timestamppb.New(*eventForm.Startdate),
|
|
||||||
Enddate: timestamppb.New(*eventForm.Enddate),
|
|
||||||
Starttime: eventForm.Starttime,
|
|
||||||
Endtime: eventForm.Endtime,
|
|
||||||
Allday: eventForm.Allday,
|
|
||||||
MaxSubscribers: int64(eventForm.MaxSubscribers),
|
|
||||||
Data: data,
|
|
||||||
Subscriptions: resp.Event.Subscriptions,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.UpdateEvent(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", resp.Event.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.AgendaUpdateEvent(w, r, resp.Event.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) AgendaDeleteEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventID := vars["eventid"]
|
|
||||||
request := &agenda.GetEventRequest{
|
|
||||||
Id: eventID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
request := &agenda.UpdateEventRequest{
|
|
||||||
Event: &agenda.Event{
|
|
||||||
Namespace: resp.Event.Namespace,
|
|
||||||
Id: resp.Event.Id,
|
|
||||||
Owners: resp.Event.Owners,
|
|
||||||
Type: resp.Event.Type,
|
|
||||||
Name: resp.Event.Name,
|
|
||||||
Description: resp.Event.Description,
|
|
||||||
Startdate: resp.Event.Startdate,
|
|
||||||
Enddate: resp.Event.Enddate,
|
|
||||||
Starttime: resp.Event.Starttime,
|
|
||||||
Endtime: resp.Event.Endtime,
|
|
||||||
Allday: resp.Event.Allday,
|
|
||||||
MaxSubscribers: int64(resp.Event.MaxSubscribers),
|
|
||||||
Data: resp.Event.Data,
|
|
||||||
Subscriptions: resp.Event.Subscriptions,
|
|
||||||
Deleted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.Agenda.UpdateEvent(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/agenda/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.AgendaDeleteEvent(w, r, resp.Event.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////Delete subscriber///////////////////////////////
|
|
||||||
func (h *ApplicationHandler) AgendaDeleteSubscribeEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventId := vars["eventid"]
|
|
||||||
subscribeid := vars["subscribeid"]
|
|
||||||
s_b_id := ""
|
|
||||||
s_b_name := ""
|
|
||||||
s_b_email := ""
|
|
||||||
s_b_group_id := ""
|
|
||||||
s_b_group_name := ""
|
|
||||||
request := &agenda.GetEventRequest{
|
|
||||||
Id: eventId,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range resp.Event.Subscriptions {
|
|
||||||
if resp.Event.Subscriptions[i].Subscriber == subscribeid {
|
|
||||||
subscribed_by_id := resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["id"].GetStringValue()
|
|
||||||
subscribed_by_name := resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["display_name"].GetStringValue()
|
|
||||||
subscribed_by_email := resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["user"].GetStructValue().Fields["email"].GetStringValue()
|
|
||||||
subscribed_by_group_id := resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["group"].GetStructValue().Fields["id"].GetStringValue()
|
|
||||||
subscribed_by_group_name := resp.Event.Subscriptions[i].Data.Fields["subscribed_by"].GetStructValue().Fields["group"].GetStructValue().Fields["name"].GetStringValue()
|
|
||||||
s_b_id = subscribed_by_id
|
|
||||||
s_b_name = subscribed_by_name
|
|
||||||
s_b_email = subscribed_by_email
|
|
||||||
s_b_group_id = subscribed_by_group_id
|
|
||||||
s_b_group_name = subscribed_by_group_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current_group, err := h.currentGroup(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_user_token, current_user_claims, err := h.currentUser(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"subscribed_by": map[string]any{
|
|
||||||
"user": map[string]any{
|
|
||||||
"id": s_b_id,
|
|
||||||
"display_name": s_b_name,
|
|
||||||
"email": s_b_email,
|
|
||||||
},
|
|
||||||
"group": map[string]any{
|
|
||||||
"id": s_b_group_id,
|
|
||||||
"name": s_b_group_name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"unsubscribed_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"],
|
|
||||||
},
|
|
||||||
"group": map[string]any{
|
|
||||||
"id": current_group.ID,
|
|
||||||
"name": current_group.Data["name"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"motif": r.FormValue("motif"),
|
|
||||||
}
|
|
||||||
|
|
||||||
datapb, err := structpb.NewStruct(data)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
request := &agenda.DeleteSubscriptionRequest{
|
|
||||||
Subscriber: subscribeid,
|
|
||||||
Eventid: eventId,
|
|
||||||
Data: datapb,
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"motif": r.FormValue("motif"),
|
|
||||||
"user": current_user_claims["first_name"].(string) + " " + current_user_claims["last_name"].(string),
|
|
||||||
"subscriber": fmt.Sprintf("http://localhost:9000/app/beneficiaries/%s", subscribeid),
|
|
||||||
"link": fmt.Sprintf("http://localhost:9000/app/agenda/%s", eventId),
|
|
||||||
}
|
|
||||||
|
|
||||||
// récupérer l'adresse mail de l'utilisateur qui a créé l'événement
|
|
||||||
mail := s_b_email
|
|
||||||
log.Debug().Str("mail", mail).Msg("Email content")
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.Agenda.DeleteSubscription(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("delete_subscriber.request", mail, data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventId), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.AgendaDeleteSubscribeEvent(w, r, eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////
|
|
||||||
// /////////////////////History Event////////////////////////
|
|
||||||
func (h *ApplicationHandler) AgendaHistoryEvent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventId := vars["eventid"]
|
|
||||||
request := &agenda.GetEventRequest{
|
|
||||||
Id: eventId,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
grouprequest := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: resp.Event.Owners[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), grouprequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribers := map[string]any{}
|
|
||||||
|
|
||||||
accids := []string{}
|
|
||||||
for _, v := range resp.Event.DeletedSubscription {
|
|
||||||
accids = append(accids, v.Subscriber)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriberresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
|
||||||
context.TODO(),
|
|
||||||
&mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: accids,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, sub := range subscriberresp.Accounts {
|
|
||||||
subscribers[sub.Id] = sub.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
accountids := []string{}
|
|
||||||
for _, m := range group.Members {
|
|
||||||
if !contains(resp.Event.DeletedSubscription, m) {
|
|
||||||
accountids = append(accountids, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(
|
|
||||||
context.TODO(),
|
|
||||||
&mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: accountids,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
accounts := []any{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, acc := range accountresp.Accounts {
|
|
||||||
accounts = append(accounts, acc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.AgendaHistoryEvent(w, r, resp.Event.ToStorageType(), groupresp.Group.ToStorageType(), subscribers, accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// //// ADD DOCUMENTS //////
|
|
||||||
func (h *ApplicationHandler) EventDocuments(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventID := vars["eventid"]
|
|
||||||
|
|
||||||
//r.ParseForm()
|
|
||||||
r.ParseMultipartForm(100 * 1024 * 1024)
|
|
||||||
|
|
||||||
document_type := r.FormValue("type")
|
|
||||||
document_name := r.FormValue("name")
|
|
||||||
|
|
||||||
file, header, err := r.FormFile("file-upload")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"type": document_type,
|
|
||||||
"name": document_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s_%s", eventID, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) EventDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventID := vars["eventid"]
|
|
||||||
document := vars["document"]
|
|
||||||
|
|
||||||
file, info, err := h.filestorage.Get(filestorage.PREFIX_AGENDA, fmt.Sprintf("%s/%s", eventID, document))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", info.ContentType)
|
|
||||||
if _, err = io.Copy(w, file); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/agenda/%s", eventID), http.StatusFound)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,706 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
profilepictures "git.coopgo.io/coopgo-apps/parcoursmob/utils/profile-pictures"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
|
||||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
|
||||||
diagsstorage "git.coopgo.io/coopgo-platform/diags/storage"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
|
||||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event_Beneficiary interface {
|
|
||||||
Name() string
|
|
||||||
Date() time.Time
|
|
||||||
DateEnd() time.Time
|
|
||||||
Type() string
|
|
||||||
Db() string
|
|
||||||
ID() string
|
|
||||||
Icons() string
|
|
||||||
Status() int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
IDVal string
|
|
||||||
NameVal string
|
|
||||||
DateVal time.Time
|
|
||||||
DateEndVal time.Time
|
|
||||||
TypeVal string
|
|
||||||
DbVal string
|
|
||||||
Deleted bool
|
|
||||||
IconSet string
|
|
||||||
StatusVal int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) Name() string {
|
|
||||||
return e.NameVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) Date() time.Time {
|
|
||||||
return e.DateVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) DateEnd() time.Time {
|
|
||||||
return e.DateEndVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) Type() string {
|
|
||||||
return e.TypeVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) ID() string {
|
|
||||||
return e.IDVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) Db() string {
|
|
||||||
return e.DbVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) Icons() string {
|
|
||||||
return e.IconSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) Status() int {
|
|
||||||
return e.StatusVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortByDate(events []Event_Beneficiary) {
|
|
||||||
sort.Slice(events, func(i, j int) bool {
|
|
||||||
return events[i].Date().After(events[j].Date())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiariesList(w http.ResponseWriter, r *http.Request) {
|
|
||||||
accounts, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.BeneficiariesByName(accounts))
|
|
||||||
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
|
|
||||||
h.Renderer.BeneficiariesList(w, r, accounts, cacheid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryCreate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
log.Error().Msg("Create beneficiary : could not find group")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
dataMap, err := parseBeneficiariesForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.RegisterRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Namespace: "parcoursmob_beneficiaries",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe := &groupsmanagement.SubscribeRequest{
|
|
||||||
Groupid: group.ID,
|
|
||||||
Memberid: resp.Account.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.Subscribe(context.TODO(), subscribe)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.BeneficiaryCreate(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiaryID)
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: beneficiaryID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("cannot retrieve account")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptionrequest := &agenda.GetSubscriptionByUserRequest{
|
|
||||||
Subscriber: beneficiaryID,
|
|
||||||
}
|
|
||||||
|
|
||||||
subcriptionresp, err := h.services.GRPC.Agenda.GetSubscriptionByUser(context.TODO(), subscriptionrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
events := []agendastorage.Event{}
|
|
||||||
currentTime := time.Now().Truncate(24 * time.Hour)
|
|
||||||
|
|
||||||
for _, e := range subcriptionresp.Subscription {
|
|
||||||
eventresquest := &agenda.GetEventRequest{
|
|
||||||
Id: e.Eventid,
|
|
||||||
}
|
|
||||||
eventresp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), eventresquest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
events = append(events, eventresp.Event.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.EventsByStartdate(events))
|
|
||||||
|
|
||||||
bookingsrequest := &fleets.GetDriverBookingsRequest{
|
|
||||||
Driver: beneficiaryID,
|
|
||||||
}
|
|
||||||
bookingsresp, err := h.services.GRPC.Fleets.GetDriverBookings(context.TODO(), bookingsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
|
|
||||||
for _, b := range bookingsresp.Bookings {
|
|
||||||
bookings = append(bookings, b.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
var events_list []Event_Beneficiary
|
|
||||||
var status_event int
|
|
||||||
|
|
||||||
for _, e := range events {
|
|
||||||
|
|
||||||
if e.Startdate.After(currentTime) {
|
|
||||||
status_event = 1
|
|
||||||
} else if e.Startdate.Before(currentTime) && e.Enddate.After(currentTime) || e.Enddate.Equal(currentTime) {
|
|
||||||
status_event = 2
|
|
||||||
} else {
|
|
||||||
status_event = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
event := Event{
|
|
||||||
NameVal: e.Name,
|
|
||||||
DateVal: e.Startdate,
|
|
||||||
DateEndVal: e.Enddate,
|
|
||||||
TypeVal: e.Type,
|
|
||||||
IDVal: e.ID,
|
|
||||||
DbVal: "/app/agenda/",
|
|
||||||
IconSet: "calendar",
|
|
||||||
StatusVal: status_event,
|
|
||||||
}
|
|
||||||
|
|
||||||
events_list = append(events_list, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
var status_booking int
|
|
||||||
for _, b := range bookings {
|
|
||||||
if b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) {
|
|
||||||
GetVehiculeRequest := &fleets.GetVehicleRequest{
|
|
||||||
Vehicleid: b.Vehicleid,
|
|
||||||
}
|
|
||||||
|
|
||||||
GetVehiculeResp, err := h.services.GRPC.Fleets.GetVehicle(context.Background(), GetVehiculeRequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Startdate.After(currentTime) {
|
|
||||||
status_booking = 1
|
|
||||||
} else if b.Startdate.Before(currentTime) && b.Enddate.After(currentTime) || b.Enddate.Equal(currentTime) {
|
|
||||||
status_booking = 2
|
|
||||||
} else {
|
|
||||||
status_booking = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
event := Event{
|
|
||||||
NameVal: GetVehiculeResp.Vehicle.ToStorageType().Data["name"].(string),
|
|
||||||
DateVal: b.Startdate,
|
|
||||||
DateEndVal: b.Enddate,
|
|
||||||
TypeVal: "Réservation de véhicule",
|
|
||||||
IDVal: b.ID,
|
|
||||||
DbVal: "/app/vehicles-management/bookings/",
|
|
||||||
IconSet: "vehicle",
|
|
||||||
StatusVal: status_booking,
|
|
||||||
}
|
|
||||||
|
|
||||||
events_list = append(events_list, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
solidarity, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBookings(context.Background(), &gen.GetSolidarityTransportBookingsRequest{
|
|
||||||
Passengerid: beneficiaryID,
|
|
||||||
StartDate: timestamppb.New(time.Now().Add(-36 * 730 * time.Hour)),
|
|
||||||
EndDate: timestamppb.New(time.Now().Add(12 * 730 * time.Hour)),
|
|
||||||
Status: "VALIDATED",
|
|
||||||
})
|
|
||||||
solidarityTransportStats := map[string]int64{
|
|
||||||
"count": 0,
|
|
||||||
"km": 0,
|
|
||||||
}
|
|
||||||
for _, s := range solidarity.Bookings {
|
|
||||||
b, _ := transformers.BookingProtoToType(s)
|
|
||||||
event := Event{
|
|
||||||
NameVal: fmt.Sprintf("%s (%d km)", b.Journey.PassengerDrop.Properties.MustString("label", ""), b.Journey.PassengerDistance),
|
|
||||||
DateVal: b.Journey.PassengerPickupDate,
|
|
||||||
DateEndVal: b.Journey.PassengerPickupDate,
|
|
||||||
TypeVal: "Transport solidaire",
|
|
||||||
IDVal: b.Id,
|
|
||||||
DbVal: "/app/solidarity-transport/bookings/",
|
|
||||||
IconSet: "vehicle",
|
|
||||||
StatusVal: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
events_list = append(events_list, event)
|
|
||||||
|
|
||||||
solidarityTransportStats["count"] = solidarityTransportStats["count"] + 1
|
|
||||||
solidarityTransportStats["km"] = solidarityTransportStats["km"] + b.Journey.PassengerDistance
|
|
||||||
|
|
||||||
}
|
|
||||||
sortByDate(events_list)
|
|
||||||
|
|
||||||
diag := []diagsstorage.Diag{}
|
|
||||||
|
|
||||||
/*diagsrequest := &diags.GetDiagsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_beneficiaries"},
|
|
||||||
}
|
|
||||||
|
|
||||||
diagsresp, err := h.services.GRPC.Diags.GetDiags(context.TODO(), diagsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range diagsresp.Diags {
|
|
||||||
diagData := d.Data.AsMap()
|
|
||||||
if beneficiary, ok := diagData["beneficiary"].(string); ok && beneficiary == beneficiaryID {
|
|
||||||
diag = append(diag, d.ToStorageType())
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
groupsrequest := &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_organizations"},
|
|
||||||
Member: beneficiaryID,
|
|
||||||
}
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), groupsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
organizations := []any{}
|
|
||||||
for _, o := range groupsresp.Groups {
|
|
||||||
organizations = append(organizations, o.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_file_types := h.config.GetStringSlice("modules.beneficiaries.documents_types")
|
|
||||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
|
||||||
|
|
||||||
diagsAny := make([]any, len(diag))
|
|
||||||
for i, d := range diag {
|
|
||||||
diagsAny[i] = d
|
|
||||||
}
|
|
||||||
|
|
||||||
account := resp.Account.ToStorageType()
|
|
||||||
walletBalance := h.calculateWalletBalance(account)
|
|
||||||
|
|
||||||
h.Renderer.BeneficiaryDisplay(w, r, account, bookings, organizations, beneficiaries_file_types, file_types_map, documents, events_list, diagsAny, solidarityTransportStats, walletBalance)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryUpdate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
dataMap, err := parseBeneficiariesForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: beneficiaryID,
|
|
||||||
Namespace: "parcoursmob_beneficiaries",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: beneficiaryID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO filter namespaces
|
|
||||||
// TODO filter groups
|
|
||||||
|
|
||||||
h.Renderer.BeneficiaryUpdate(w, r, resp.Account.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryArchive(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(map[string]any{
|
|
||||||
"archived": true,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: beneficiaryID,
|
|
||||||
Namespace: "parcoursmob_beneficiaries",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryUnarchive(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(map[string]any{
|
|
||||||
"archived": false,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: beneficiaryID,
|
|
||||||
Namespace: "parcoursmob_beneficiaries",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryPicture(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: beneficiaryID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
account := resp.Account.ToStorageType()
|
|
||||||
|
|
||||||
firstName := account.Data["first_name"].(string)
|
|
||||||
lastName := account.Data["last_name"].(string)
|
|
||||||
picture := profilepictures.DefaultProfilePicture(strings.ToUpper(firstName[0:1] + lastName[0:1]))
|
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
if err := png.Encode(buffer, picture); err != nil {
|
|
||||||
log.Error().Err(err).Msg("unable to encode image.")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "image/png")
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))
|
|
||||||
if _, err := w.Write(buffer.Bytes()); err != nil {
|
|
||||||
log.Error().Err(err).Msg("unable to write image.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryDocuments(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
// r.ParseForm()
|
|
||||||
r.ParseMultipartForm(100 * 1024 * 1024)
|
|
||||||
|
|
||||||
document_type := r.FormValue("type")
|
|
||||||
document_name := r.FormValue("name")
|
|
||||||
|
|
||||||
file, header, err := r.FormFile("file-upload")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"type": document_type,
|
|
||||||
"name": document_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Any("metadata", metadata).Msg("Metadata")
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s_%s", beneficiaryID, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiaryDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
document := vars["document"]
|
|
||||||
|
|
||||||
file, info, err := h.filestorage.Get(filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s", beneficiaryID, document))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", info.ContentType)
|
|
||||||
if _, err = io.Copy(w, file); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterAccount(r *http.Request, a *mobilityaccounts.Account) bool {
|
|
||||||
searchFilter, ok := r.URL.Query()["search"]
|
|
||||||
|
|
||||||
if ok && len(searchFilter[0]) > 0 {
|
|
||||||
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
|
|
||||||
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter[0])) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
archivedFilter, ok := r.URL.Query()["archived"]
|
|
||||||
if ok && archivedFilter[0] == "true" {
|
|
||||||
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
if archived, ok := a.Data.AsMap()["archived"].(bool); ok && archived {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// func BeneficiariesEventList()
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
|
|
||||||
accounts := []mobilityaccountsstorage.Account{}
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
return accounts, errors.New("no group provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: group.Members,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("issue in mobilityaccounts call")
|
|
||||||
return accounts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range resp.Accounts {
|
|
||||||
if filterAccount(r, account) {
|
|
||||||
a := account.ToStorageType()
|
|
||||||
accounts = append(accounts, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return accounts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBeneficiariesForm(r *http.Request) (map[string]any, 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"),
|
|
||||||
}
|
|
||||||
log.Trace().Any("address", r.PostFormValue("address")).Msg("address")
|
|
||||||
|
|
||||||
if r.PostFormValue("address") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.PostFormValue("address")), &a)
|
|
||||||
|
|
||||||
formData.Address = a
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace().Any("otther properties", r.PostFormValue("other_properties")).Msg("other propperties ?")
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := json.Marshal(formData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataMap map[string]any
|
|
||||||
err = json.Unmarshal(d, &dataMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataMap, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
|
||||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
fleetstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: group.Members,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts := []any{}
|
|
||||||
|
|
||||||
// We only display the 10 last here
|
|
||||||
count := len(resp.Accounts)
|
|
||||||
min := count - 5
|
|
||||||
if min < 0 {
|
|
||||||
min = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range resp.Accounts[min:] {
|
|
||||||
if filterAccount(r, account) {
|
|
||||||
fmt.Println(account)
|
|
||||||
a := account.ToStorageType()
|
|
||||||
accounts = append([]any{a}, accounts...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
members, _, err := h.groupmembers(group.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
count_members := len(members)
|
|
||||||
|
|
||||||
events := []agendastorage.Event{}
|
|
||||||
|
|
||||||
eventsresp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
Mindate: timestamppb.Now(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, e := range eventsresp.Events {
|
|
||||||
events = append(events, e.ToStorageType())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.EventsByStartdate(events))
|
|
||||||
|
|
||||||
bookings := []fleetstorage.Booking{}
|
|
||||||
|
|
||||||
bookingsresp, err := h.services.GRPC.Fleets.GetBookings(context.TODO(), &fleets.GetBookingsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, b := range bookingsresp.Bookings {
|
|
||||||
if b.Enddate.AsTime().After(time.Now()) {
|
|
||||||
bookings = append(bookings, b.ToStorageType())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.Dashboard(w, r, accounts, count, count_members, events, bookings)
|
|
||||||
}
|
|
||||||
|
|
@ -1,642 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
// "io"
|
|
||||||
"net/http"
|
|
||||||
// "strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
|
|
||||||
// filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
diags "git.coopgo.io/coopgo-platform/diags/grpcapi"
|
|
||||||
diagsstorage "git.coopgo.io/coopgo-platform/diags/storage"
|
|
||||||
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
// mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
// "github.com/google/uuid"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DiagsForm struct {
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Namespace string `json:"namespace" validate:"required"`
|
|
||||||
JsonSchema string `json:"json_schema"`
|
|
||||||
UiSchema string `json:"ui_schema"`
|
|
||||||
Data map[string]any `json:"data"`
|
|
||||||
Deleted bool `json:"deleted"`
|
|
||||||
Diagdate *time.Time `json:"diagdate"`
|
|
||||||
Owners []string `json:"owners"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagsHome(w http.ResponseWriter, r *http.Request) {
|
|
||||||
groupID := r.Context().Value(identification.GroupKey).(storage.Group).ID
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.GetDiags(context.TODO(), &diags.GetDiagsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_beneficiaries", "parcoursmob_diagnostiques", "parcoursmob_vehicles", "parcoursmob_bookings"},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responses := []diagsstorage.Diag{}
|
|
||||||
|
|
||||||
groupids := []string{}
|
|
||||||
for _, e := range resp.Diags {
|
|
||||||
groupids = append(groupids, e.Owners...)
|
|
||||||
responses = append(responses, e.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, g := range groupsresp.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredDiags := []diagsstorage.Diag{}
|
|
||||||
for _, diag := range responses {
|
|
||||||
for _, owner := range diag.Owners {
|
|
||||||
if string(owner) == groupID {
|
|
||||||
filteredDiags = append(filteredDiags, diag)
|
|
||||||
log.Debug().Msgf("Diag %s added to filtered list", diag.ID)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.DiagsHome(w, r, filteredDiags, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagsHistory(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp, err := h.services.GRPC.Diags.GetDiags(context.TODO(), &diags.GetDiagsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_beneficiaries", "parcoursmob_diagnostiques", "parcoursmob_vehicles", "parcoursmob_bookings"},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responses := []diagsstorage.Diag{}
|
|
||||||
|
|
||||||
for _, e := range resp.Diags {
|
|
||||||
if e.Deleted {
|
|
||||||
responses = append(responses, e.ToStorageType())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.DiagsHistory(w, r, responses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BeneficiariesCreateDiag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
beneficiaryID := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
if h.services == nil || (h.services.GRPC == services.GRPCServices{}) || h.services.GRPC.Diags == nil {
|
|
||||||
log.Error().Msg("Diags service is not initialized")
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
http.Error(w, "Missing group information", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
diagForm, err := parseDiagsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Invalid form data")
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewStruct(map[string]any{
|
|
||||||
"beneficiary": beneficiaryID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create protobuf struct")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &diags.CreateDiagRequest{
|
|
||||||
Diag: &diags.Diag{
|
|
||||||
Name: diagForm.Name,
|
|
||||||
Namespace: diagForm.Namespace,
|
|
||||||
JsonSchema: diagForm.JsonSchema,
|
|
||||||
UiSchema: diagForm.UiSchema,
|
|
||||||
Data: data,
|
|
||||||
Deleted: diagForm.Deleted,
|
|
||||||
Diagdate: timestamppb.New(time.Now()),
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.CreateDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create diagnostic")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, header, err := r.FormFile("file-upload")
|
|
||||||
if err == nil {
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
document_type := r.FormValue("file_type")
|
|
||||||
document_name := r.FormValue("file_name")
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"file_type": document_type,
|
|
||||||
"file_name": document_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_DIAGS, fmt.Sprintf("%s/%s_%s", resp.Diag.Id, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Error uploading file")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err != http.ErrMissingFile {
|
|
||||||
log.Error().Err(err).Msg("Error retrieving file")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", resp.Diag.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.BeneficiariesCreateDiag(w, r, beneficiaryID, h.config.GetStringSlice("modules.diags.documents_types"), h.config.GetStringMapString("storage.files.file_types"), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehiclesCreateDiag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
vehicleID := vars["vehicleid"]
|
|
||||||
|
|
||||||
if h.services == nil || (h.services.GRPC == services.GRPCServices{}) || h.services.GRPC.Diags == nil {
|
|
||||||
log.Error().Msg("Diags service is not initialized")
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
http.Error(w, "Missing group information", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
diagForm, err := parseDiagsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Invalid form data")
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Interface("diagForm", diagForm).Msg("Form data parsed")
|
|
||||||
|
|
||||||
data, err := structpb.NewStruct(map[string]any{
|
|
||||||
"vehicle": vehicleID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create protobuf struct")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
request := &diags.CreateDiagRequest{
|
|
||||||
Diag: &diags.Diag{
|
|
||||||
Name: diagForm.Name,
|
|
||||||
Namespace: diagForm.Namespace,
|
|
||||||
JsonSchema: diagForm.JsonSchema,
|
|
||||||
UiSchema: diagForm.UiSchema,
|
|
||||||
Data: data,
|
|
||||||
Deleted: diagForm.Deleted,
|
|
||||||
Diagdate: timestamppb.New(time.Now()),
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.CreateDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create diagnostic")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", resp.Diag.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.VehiclesCreateDiag(w, r, vehicleID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BookingsCreateDiag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingID := vars["bookingid"]
|
|
||||||
|
|
||||||
if h.services == nil || (h.services.GRPC == services.GRPCServices{}) || h.services.GRPC.Diags == nil {
|
|
||||||
log.Error().Msg("Diags service is not initialized")
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
http.Error(w, "Missing group information", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
diagForm, err := parseDiagsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Invalid form data")
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Interface("diagForm", diagForm).Msg("Form data parsed")
|
|
||||||
|
|
||||||
data, err := structpb.NewStruct(map[string]any{
|
|
||||||
"booking": bookingID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create protobuf struct")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
request := &diags.CreateDiagRequest{
|
|
||||||
Diag: &diags.Diag{
|
|
||||||
Name: diagForm.Name,
|
|
||||||
Namespace: diagForm.Namespace,
|
|
||||||
JsonSchema: diagForm.JsonSchema,
|
|
||||||
UiSchema: diagForm.UiSchema,
|
|
||||||
Data: data,
|
|
||||||
Deleted: diagForm.Deleted,
|
|
||||||
Diagdate: timestamppb.New(time.Now()),
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.CreateDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create diagnostic")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", resp.Diag.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.BookingsCreateDiag(w, r, bookingID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehicleBookingsCreateDiag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingID := vars["bookingid"]
|
|
||||||
|
|
||||||
if h.services == nil || (h.services.GRPC == services.GRPCServices{}) || h.services.GRPC.Diags == nil {
|
|
||||||
log.Error().Msg("Diags service is not initialized")
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
http.Error(w, "Missing group information", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
diagForm, err := parseDiagsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Invalid form data")
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Interface("diagForm", diagForm).Msg("Form data parsed")
|
|
||||||
|
|
||||||
data, err := structpb.NewStruct(map[string]any{
|
|
||||||
"booking": bookingID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create protobuf struct")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
request := &diags.CreateDiagRequest{
|
|
||||||
Diag: &diags.Diag{
|
|
||||||
Name: diagForm.Name,
|
|
||||||
Namespace: diagForm.Namespace,
|
|
||||||
JsonSchema: diagForm.JsonSchema,
|
|
||||||
UiSchema: diagForm.UiSchema,
|
|
||||||
Data: data,
|
|
||||||
Deleted: diagForm.Deleted,
|
|
||||||
Diagdate: timestamppb.New(time.Now()),
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.CreateDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to create diagnostic")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", resp.Diag.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.VehicleBookingsCreateDiag(w, r, bookingID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagsDisplayDiag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
diagid := vars["diagid"]
|
|
||||||
|
|
||||||
request := &diags.GetDiagRequest{
|
|
||||||
Id: diagid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.GetDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_DIAGS + "/" + diagid)
|
|
||||||
|
|
||||||
events_file_types := h.config.GetStringSlice("modules.diags.documents_types")
|
|
||||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
|
||||||
|
|
||||||
h.Renderer.DiagsDisplayDiag(w, r, resp.Diag.ToStorageType(), events_file_types, file_types_map, documents)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDiagsForm(r *http.Request) (*DiagsForm, error) {
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
formData := &DiagsForm{
|
|
||||||
Name: r.PostFormValue("name"),
|
|
||||||
Namespace: r.PostFormValue("namespace"), // Récupère le namespace
|
|
||||||
JsonSchema: r.PostFormValue("json_schema"),
|
|
||||||
UiSchema: r.PostFormValue("ui_schema"),
|
|
||||||
Deleted: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if formData.Name == "" || formData.Namespace == "" {
|
|
||||||
return nil, errors.New("missing required fields: 'name' or 'namespace'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gestion de la valeur JSON dans `data`
|
|
||||||
if rawData := r.PostFormValue("data"); rawData != "" {
|
|
||||||
data := map[string]any{}
|
|
||||||
if err := json.Unmarshal([]byte(rawData), &data); err != nil {
|
|
||||||
return nil, errors.New("invalid 'data' field: must be a valid JSON object")
|
|
||||||
}
|
|
||||||
formData.Data = data
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagUpdate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
adm := strings.Split(r.URL.Path, "/")
|
|
||||||
diagID := adm[3]
|
|
||||||
request := &diags.GetDiagRequest{
|
|
||||||
Id: diagID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.GetDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method == "POST" {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
diagForm, err := parseDiagsForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := structpb.NewStruct(map[string]any{})
|
|
||||||
|
|
||||||
request := &diags.UpdateDiagRequest{
|
|
||||||
Diag: &diags.Diag{
|
|
||||||
Namespace: diagForm.Namespace,
|
|
||||||
Id: diagID,
|
|
||||||
Name: diagForm.Name,
|
|
||||||
JsonSchema: diagForm.JsonSchema,
|
|
||||||
UiSchema: diagForm.UiSchema,
|
|
||||||
Data: data,
|
|
||||||
Diagdate: timestamppb.New(time.Now()),
|
|
||||||
Owners: []string{group.ID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.UpdateDiag(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", resp.Diag.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.DiagUpdate(w, r, resp.Diag.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagDelete(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
diagID := vars["diagid"]
|
|
||||||
request := &diags.GetDiagRequest{
|
|
||||||
Id: diagID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.GetDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
request := &diags.UpdateDiagRequest{
|
|
||||||
Diag: &diags.Diag{
|
|
||||||
Namespace: resp.Diag.Namespace,
|
|
||||||
Id: resp.Diag.Id,
|
|
||||||
Name: resp.Diag.Name,
|
|
||||||
JsonSchema: resp.Diag.JsonSchema,
|
|
||||||
UiSchema: resp.Diag.UiSchema,
|
|
||||||
Data: resp.Diag.Data,
|
|
||||||
Deleted: true,
|
|
||||||
Diagdate: resp.Diag.Diagdate,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.Diags.UpdateDiag(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/diags/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.DiagDelete(w, r, resp.Diag.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagsHistoryDiag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
diagId := vars["diagid"]
|
|
||||||
request := &diags.GetDiagRequest{
|
|
||||||
Id: diagId,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Diags.GetDiag(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.DiagsHistoryDiag(w, r, resp.Diag.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
// //// ADD DOCUMENTS //////
|
|
||||||
func (h *ApplicationHandler) DiagsDocuments(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
diagID := vars["diagid"]
|
|
||||||
|
|
||||||
//r.ParseForm()
|
|
||||||
r.ParseMultipartForm(100 * 1024 * 1024)
|
|
||||||
|
|
||||||
document_type := r.FormValue("type")
|
|
||||||
document_name := r.FormValue("name")
|
|
||||||
|
|
||||||
file, header, err := r.FormFile("file-upload")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"type": document_type,
|
|
||||||
"name": document_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_DIAGS, fmt.Sprintf("%s/%s_%s", diagID, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", diagID), http.StatusFound)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DiagsDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
diagID := vars["diagid"]
|
|
||||||
document := vars["document"]
|
|
||||||
|
|
||||||
file, info, err := h.filestorage.Get(filestorage.PREFIX_DIAGS, fmt.Sprintf("%s/%s", diagID, document))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", info.ContentType)
|
|
||||||
if _, err = io.Copy(w, file); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/diags/%s", diagID), http.StatusFound)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DirectoryHome(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.Renderer.DirectoryHome(w, r)
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) GroupSettingsDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
members, err := h.members()
|
|
||||||
if err != nil {
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
admins := []any{}
|
|
||||||
groupMembers := []any{}
|
|
||||||
|
|
||||||
for _, m := range members {
|
|
||||||
mm := m.ToStorageType()
|
|
||||||
for _, g := range mm.Data["groups"].([]any) {
|
|
||||||
if g.(string) == group.ID {
|
|
||||||
groupMembers = append(groupMembers, mm)
|
|
||||||
}
|
|
||||||
if g.(string) == group.ID+":admin" {
|
|
||||||
admins = append(admins, mm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.GroupSettingsDisplay(w, r, group, groupMembers, admins)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) GroupSettingsInviteMember(w http.ResponseWriter, r *http.Request) {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &accounts.GetAccountUsernameRequest{
|
|
||||||
Username: r.FormValue("username"),
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// Account already exists : adding the existing account to admin list
|
|
||||||
account := accountresp.Account.ToStorageType()
|
|
||||||
//account.Data["groups"] = append(account.Data["groups"].([]any), groupid, groupid)
|
|
||||||
account.Data["groups"] = append(account.Data["groups"].([]any), group.ID)
|
|
||||||
|
|
||||||
as, _ := accounts.AccountFromStorageType(&account)
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.MobilityAccounts.UpdateData(
|
|
||||||
context.TODO(),
|
|
||||||
&accounts.UpdateDataRequest{
|
|
||||||
Account: as,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"group": group.Data["name"],
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("onboarding.existing_member", r.FormValue("username"), data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/group/settings", http.StatusFound)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// Onboard now administrator
|
|
||||||
onboarding := map[string]any{
|
|
||||||
"username": r.FormValue("username"),
|
|
||||||
"group": group.ID,
|
|
||||||
"admin": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, 16)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key := base64.RawURLEncoding.EncodeToString(b)
|
|
||||||
|
|
||||||
h.cache.PutWithTTL("onboarding/"+key, onboarding, 72*time.Hour)
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"group": group.Data["name"],
|
|
||||||
"key": key,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.emailing.Send("onboarding.new_member", r.FormValue("username"), data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/group/settings", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Addres any
|
|
||||||
|
|
||||||
type BeneficiariesGroupForm 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"`
|
|
||||||
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
|
|
||||||
Address any `json:"address,omitempty"`
|
|
||||||
Gender string `json:"gender"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupsModuleByName []groupstorage.Group
|
|
||||||
|
|
||||||
func (a GroupsModuleByName) Len() int { return len(a) }
|
|
||||||
func (a GroupsModuleByName) Less(i, j int) bool {
|
|
||||||
return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
|
|
||||||
}
|
|
||||||
func (a GroupsModuleByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) Groups(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
request := &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_groups"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var groups = []groupstorage.Group{}
|
|
||||||
|
|
||||||
for _, group := range resp.Groups {
|
|
||||||
g := group.ToStorageType()
|
|
||||||
groups = append(groups, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(GroupsModuleByName(groups))
|
|
||||||
|
|
||||||
h.Renderer.Groups(w, r, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) CreateGroupModule(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
if r.PostFormValue("address") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.PostFormValue("address")), &a)
|
|
||||||
|
|
||||||
Addres = a
|
|
||||||
}
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
if r.FormValue("name") == "" {
|
|
||||||
log.Error().Msg("Invalid name")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.FormValue("type") == "" {
|
|
||||||
log.Error().Msg("Invalid type")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groupid := uuid.NewString()
|
|
||||||
|
|
||||||
dataMap := map[string]any{
|
|
||||||
"name": r.FormValue("name"),
|
|
||||||
"type": r.FormValue("type"),
|
|
||||||
"description": r.FormValue("description"),
|
|
||||||
"address": Addres,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request_organization := &groupsmanagement.AddGroupRequest{
|
|
||||||
Group: &groupsmanagement.Group{
|
|
||||||
Id: groupid,
|
|
||||||
Namespace: "parcoursmob_groups",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.AddGroup(context.TODO(), request_organization)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/group_module/groups/%s", groupid), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
group_types := h.config.GetStringSlice("modules.groups.group_types")
|
|
||||||
h.Renderer.CreateGroupModule(w, r, group_types)
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterAcccount(r *http.Request, a *mobilityaccounts.Account) bool {
|
|
||||||
searchFilter, ok := r.URL.Query()["search"]
|
|
||||||
|
|
||||||
if ok && len(searchFilter[0]) > 0 {
|
|
||||||
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
|
|
||||||
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter[0])) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
func (h *ApplicationHandler) DisplayGroupModule(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
|
|
||||||
request := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: groupid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var accounts = []any{}
|
|
||||||
|
|
||||||
requesst := &mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: resp.Group.Members,
|
|
||||||
}
|
|
||||||
|
|
||||||
ressp, _ := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), requesst)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
for _, account := range ressp.Accounts {
|
|
||||||
if filterAcccount(r, account) {
|
|
||||||
a := account.ToStorageType()
|
|
||||||
accounts = append(accounts, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
var beneficiary any
|
|
||||||
|
|
||||||
searched := false
|
|
||||||
|
|
||||||
// if r.Method == "POST" {
|
|
||||||
if r.FormValue("beneficiaryid") != "" {
|
|
||||||
// Handler form
|
|
||||||
searched = true
|
|
||||||
|
|
||||||
requestbeneficiary := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: r.FormValue("beneficiaryid"),
|
|
||||||
}
|
|
||||||
|
|
||||||
respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary = respbeneficiary.Account.ToStorageType()
|
|
||||||
|
|
||||||
subscribe := &groupsmanagement.SubscribeRequest{
|
|
||||||
Groupid: resp.Group.ToStorageType().ID,
|
|
||||||
Memberid: respbeneficiary.Account.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.Subscribe(context.TODO(), subscribe)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/group_module/groups/%s", resp.Group.ToStorageType().ID), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accountsBeneficaire, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//h.Renderer.BeneficaireSearch(w, r, accounts, searched, beneficiary, resp.Group.ToStorageType())
|
|
||||||
h.Renderer.DisplayGroupModule(w, r, resp.Group.ToStorageType().ID, accounts, cacheid, searched, beneficiary, resp.Group.ToStorageType(), accountsBeneficaire)
|
|
||||||
}
|
|
||||||
|
|
@ -1,709 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
carpoolproto "git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit/transitous"
|
|
||||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
|
||||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/paulmach/orb/geojson"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
//"gitlab.scity.coop/maas/navitia-golang"
|
|
||||||
//"gitlab.scity.coop/maas/navitia-golang/types"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Depart any
|
|
||||||
Arrive any
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.ParseForm()
|
|
||||||
var (
|
|
||||||
transit_results []*transitous.Itinerary
|
|
||||||
carpool_results []*geojson.FeatureCollection
|
|
||||||
vehicle_results []fleetsstorage.Vehicle
|
|
||||||
)
|
|
||||||
// availableDrivers := []mobilityaccountsstorage.Account{}
|
|
||||||
drivers := map[string]mobilityaccountsstorage.Account{}
|
|
||||||
driverJourneys := []*gen.SolidarityTransportDriverJourney{}
|
|
||||||
var organizedCarpools []*carpoolproto.CarpoolServiceDriverJourney
|
|
||||||
// navitiaCh := make(chan *navitia.JourneyResults, 1)
|
|
||||||
// carpoolCh := make(chan []any, 1)
|
|
||||||
locTime, errTime := time.LoadLocation("Europe/Paris")
|
|
||||||
if errTime != nil {
|
|
||||||
log.Panic().Err(errTime).Msg("Tried to load timezone location Europe/Paris. Error. Missing zones in container ?")
|
|
||||||
}
|
|
||||||
|
|
||||||
departuredate := r.FormValue("departuredate")
|
|
||||||
departuretime := r.FormValue("departuretime")
|
|
||||||
departuredatetime, _ := time.ParseInLocation("2006-01-02 15:04", fmt.Sprintf("%s %s", departuredate, departuretime), locTime)
|
|
||||||
|
|
||||||
departure := r.FormValue("departure")
|
|
||||||
destination := r.FormValue("destination")
|
|
||||||
kb_results := []any{}
|
|
||||||
|
|
||||||
passengerid := r.FormValue("passengerid")
|
|
||||||
solidarityTransportExcludeDriver := r.FormValue("solidarity_transport_exclude_driver")
|
|
||||||
|
|
||||||
var (
|
|
||||||
departuregeo *geojson.Feature
|
|
||||||
destinationgeo *geojson.Feature
|
|
||||||
// journeys *navitia.JourneyResults
|
|
||||||
// carpoolresults []any
|
|
||||||
)
|
|
||||||
|
|
||||||
if departure == "" && passengerid != "" {
|
|
||||||
log.Debug().Msg("departure empty and passengerid set")
|
|
||||||
p, err := h.services.GetAccount(passengerid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not retrieve passenger")
|
|
||||||
w.WriteHeader(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
searched := false
|
|
||||||
if departure != "" {
|
|
||||||
log.Debug().Str("departure", departure).Msg("departure value")
|
|
||||||
departuregeo, err = geojson.UnmarshalFeature([]byte(departure))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error unmarshalling departure")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if destination != "" {
|
|
||||||
destinationgeo, err = geojson.UnmarshalFeature([]byte(destination))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error unmarshalling destination")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if departuredate != "" && departuretime != "" && departure != "" && destination != "" {
|
|
||||||
searched = true
|
|
||||||
|
|
||||||
// SOLIDARITY TRANSPORT
|
|
||||||
drivers, err = h.services.GetAccountsInNamespacesMap([]string{"solidarity_drivers", "organized_carpool_drivers"})
|
|
||||||
if err != nil {
|
|
||||||
drivers = map[string]mobilityaccountsstorage.Account{}
|
|
||||||
}
|
|
||||||
|
|
||||||
protodep, _ := transformers.GeoJsonToProto(departuregeo)
|
|
||||||
protodest, _ := transformers.GeoJsonToProto(destinationgeo)
|
|
||||||
|
|
||||||
log.Debug().Time("departure time", departuredatetime).Msg("calling driver journeys with ...")
|
|
||||||
|
|
||||||
res, err := h.services.GRPC.SolidarityTransport.GetDriverJourneys(context.Background(), &gen.GetDriverJourneysRequest{
|
|
||||||
Departure: protodep,
|
|
||||||
Arrival: protodest,
|
|
||||||
DepartureDate: timestamppb.New(departuredatetime),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error in grpc call to GetDriverJourneys")
|
|
||||||
} else {
|
|
||||||
driverJourneys = slices.Collect(func(yield func(*gen.SolidarityTransportDriverJourney) bool) {
|
|
||||||
for _, dj := range res.DriverJourneys {
|
|
||||||
if a, ok := drivers[dj.DriverId].Data["archived"]; ok {
|
|
||||||
if archived, ok := a.(bool); ok {
|
|
||||||
if archived {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dj.DriverId == solidarityTransportExcludeDriver {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !yield(dj) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// res.DriverJourneys
|
|
||||||
sort.Slice(driverJourneys, func(i, j int) bool {
|
|
||||||
return driverJourneys[i].DriverDistance < driverJourneys[j].DriverDistance
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get departure and destination addresses from properties
|
|
||||||
var departureAddress, destinationAddress string
|
|
||||||
if departuregeo.Properties != nil {
|
|
||||||
if label, ok := departuregeo.Properties["label"].(string); ok {
|
|
||||||
departureAddress = label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if destinationgeo.Properties != nil {
|
|
||||||
if label, ok := destinationgeo.Properties["label"].(string); ok {
|
|
||||||
destinationAddress = label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ORGANIZED CARPOOL
|
|
||||||
organizedCarpoolsRes, err := h.services.GRPC.CarpoolService.DriverJourneys(context.Background(), &carpoolproto.DriverJourneysRequest{
|
|
||||||
DepartureLat: departuregeo.Point().Lat(),
|
|
||||||
DepartureLng: departuregeo.Point().Lon(),
|
|
||||||
ArrivalLat: destinationgeo.Point().Lat(),
|
|
||||||
ArrivalLng: destinationgeo.Point().Lon(),
|
|
||||||
DepartureDate: timestamppb.New(departuredatetime),
|
|
||||||
DepartureAddress: &departureAddress,
|
|
||||||
ArrivalAddress: &destinationAddress,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error retrieving organized carpools")
|
|
||||||
} else {
|
|
||||||
organizedCarpools = organizedCarpoolsRes.DriverJourneys
|
|
||||||
sort.Slice(organizedCarpools, func(i, j int) bool {
|
|
||||||
return *organizedCarpools[i].Distance < *organizedCarpools[j].Distance
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
// CARPOOL OPERATORS
|
|
||||||
carpools := make(chan *geojson.FeatureCollection)
|
|
||||||
go h.services.InteropCarpool.Search(carpools, *departuregeo, *destinationgeo, departuredatetime)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for c := range carpools {
|
|
||||||
carpool_results = append(carpool_results, c)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// TRANSIT
|
|
||||||
transitch := make(chan *transitous.Itinerary)
|
|
||||||
go func(transitch chan *transitous.Itinerary, departure *geojson.Feature, destination *geojson.Feature, datetime *time.Time) {
|
|
||||||
defer close(transitch)
|
|
||||||
response, err := h.services.TransitRouting.PlanWithResponse(context.Background(), &transitous.PlanParams{
|
|
||||||
FromPlace: fmt.Sprintf("%f,%f", departure.Point().Lat(), departure.Point().Lon()),
|
|
||||||
ToPlace: fmt.Sprintf("%f,%f", destination.Point().Lat(), destination.Point().Lon()),
|
|
||||||
Time: datetime,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error retrieving transit data from Transitous server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, i := range response.Itineraries {
|
|
||||||
transitch <- &i
|
|
||||||
}
|
|
||||||
}(transitch, departuregeo, destinationgeo, &departuredatetime)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for itinerary := range transitch {
|
|
||||||
transit_results = append(transit_results, itinerary)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// VEHICLES
|
|
||||||
vehiclech := make(chan fleetsstorage.Vehicle)
|
|
||||||
go h.vehicleRequest(vehiclech, departuredatetime.Add(-24*time.Hour), departuredatetime.Add(168*time.Hour))
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for vehicle := range vehiclech {
|
|
||||||
vehicle_results = append(vehicle_results, vehicle)
|
|
||||||
}
|
|
||||||
slices.SortFunc(vehicle_results, sorting.VehiclesByDistanceFrom(*departuregeo))
|
|
||||||
}()
|
|
||||||
// journeys_results = <-navitiaCh
|
|
||||||
wg.Wait()
|
|
||||||
// KNOWLEDGE BASE
|
|
||||||
departureGeo, _ := h.services.Geography.GeoSearch(departuregeo)
|
|
||||||
kbData := h.config.Get("knowledge_base")
|
|
||||||
if kb, ok := kbData.([]any); ok {
|
|
||||||
log.Debug().Msg("1")
|
|
||||||
for _, sol := range kb {
|
|
||||||
log.Debug().Msg("2")
|
|
||||||
if solution, ok := sol.(map[string]any); ok {
|
|
||||||
log.Debug().Msg("3")
|
|
||||||
if g, ok := solution["geography"]; ok {
|
|
||||||
log.Debug().Msg("4")
|
|
||||||
if geography, ok := g.([]any); ok {
|
|
||||||
log.Debug().Msg("5")
|
|
||||||
for _, gg := range geography {
|
|
||||||
log.Debug().Msg("6")
|
|
||||||
if geog, ok := gg.(map[string]any); ok {
|
|
||||||
log.Debug().Msg("7")
|
|
||||||
if layer, ok := geog["layer"].(string); ok {
|
|
||||||
log.Debug().Msg("8")
|
|
||||||
code := geog["code"]
|
|
||||||
log.Debug().Any("code", fmt.Sprintf("%s", code)).Any("departure geo", departureGeo[layer].Properties.MustString("code")).Msg("9")
|
|
||||||
if strings.Compare(fmt.Sprintf("%v", code), departureGeo[layer].Properties.MustString("code")) == 0 {
|
|
||||||
log.Debug().Msg("10")
|
|
||||||
kb_results = append(kb_results, solution)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("issue retrieving beneficiaries")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.JourneysSearch(w, r, carpool_results, transit_results, vehicle_results, searched, departuregeo, destinationgeo, departuredate, departuretime, driverJourneys, drivers, organizedCarpools, beneficiaries, kb_results, passengerid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) vehicleRequest(vehiclech chan fleetsstorage.Vehicle, start time.Time, end time.Time) {
|
|
||||||
defer close(vehiclech)
|
|
||||||
vehiclerequest := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
vehicleresp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), vehiclerequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, vehicle := range vehicleresp.Vehicles {
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
if v.Free(start, end) {
|
|
||||||
vehiclech <- v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupsModule []groupstorage.Group
|
|
||||||
|
|
||||||
func (a GroupsModule) Len() int { return len(a) }
|
|
||||||
func (a GroupsModule) Less(i, j int) bool {
|
|
||||||
return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
|
|
||||||
}
|
|
||||||
func (a GroupsModule) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) GroupsGestion(w http.ResponseWriter, r *http.Request) {
|
|
||||||
request := &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_groups_covoiturage"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := []groupstorage.Group{}
|
|
||||||
|
|
||||||
for _, group := range resp.Groups {
|
|
||||||
g := group.ToStorageType()
|
|
||||||
groups = append(groups, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
|
|
||||||
sort.Sort(GroupsModule(groups))
|
|
||||||
h.cache.PutWithTTL(cacheid, groups, 1*time.Hour)
|
|
||||||
|
|
||||||
h.Renderer.GroupsGestion(w, r, groups, cacheid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterAcc(r *http.Request, a *mobilityaccounts.Account) bool {
|
|
||||||
searchFilter, ok := r.URL.Query()["search"]
|
|
||||||
|
|
||||||
if ok && len(searchFilter[0]) > 0 {
|
|
||||||
name := a.Data.AsMap()["first_name"].(string) + " " + a.Data.AsMap()["last_name"].(string)
|
|
||||||
if !strings.Contains(strings.ToLower(name), strings.ToLower(searchFilter[0])) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var beneficiary any
|
|
||||||
var (
|
|
||||||
departurgeo *geojson.Feature
|
|
||||||
dstinationgeo *geojson.Feature
|
|
||||||
)
|
|
||||||
searched := false
|
|
||||||
|
|
||||||
if r.FormValue("beneficiaryid") != "" {
|
|
||||||
|
|
||||||
searched = true
|
|
||||||
|
|
||||||
requestbeneficiary := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: r.FormValue("beneficiaryid"),
|
|
||||||
}
|
|
||||||
|
|
||||||
respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary = respbeneficiary.Account.ToStorageType()
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
departure := r.FormValue("departure")
|
|
||||||
destination := r.FormValue("destination")
|
|
||||||
|
|
||||||
if departure != "" && destination != "" {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
departurgeo, err = geojson.UnmarshalFeature([]byte(departure))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dstinationgeo, err = geojson.UnmarshalFeature([]byte(destination))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.FormValue("departure") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.FormValue("departure")), &a)
|
|
||||||
|
|
||||||
Depart = a
|
|
||||||
}
|
|
||||||
if r.FormValue("destination") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.FormValue("destination")), &a)
|
|
||||||
|
|
||||||
Arrive = a
|
|
||||||
}
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
if r.FormValue("name") == "" {
|
|
||||||
|
|
||||||
log.Error().Msg("invalid name")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.FormValue("number") == "" {
|
|
||||||
|
|
||||||
log.Error().Msg("invalid number of personne")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
planDays := map[string]any{
|
|
||||||
"lundi": r.FormValue("lundi") == "on",
|
|
||||||
"mardi": r.FormValue("mardi") == "on",
|
|
||||||
"mercredi": r.FormValue("mercredi") == "on",
|
|
||||||
"jeudi": r.FormValue("jeudi") == "on",
|
|
||||||
"vendredi": r.FormValue("vendredi") == "on",
|
|
||||||
"samedi": r.FormValue("samedi") == "on",
|
|
||||||
"dimanche": r.FormValue("dimanche") == "on",
|
|
||||||
}
|
|
||||||
|
|
||||||
groupidd := uuid.NewString()
|
|
||||||
|
|
||||||
dataMap := map[string]any{
|
|
||||||
"name": r.FormValue("name"),
|
|
||||||
"number": r.FormValue("number"),
|
|
||||||
"driver_first_name": respbeneficiary.Account.ToStorageType().Data["first_name"],
|
|
||||||
"driver_last_name": respbeneficiary.Account.ToStorageType().Data["last_name"],
|
|
||||||
"depart": Depart,
|
|
||||||
"arrive": Arrive,
|
|
||||||
"departdate": r.FormValue("departdate"),
|
|
||||||
"date": r.FormValue("date"),
|
|
||||||
"enddate": r.FormValue("enddate"),
|
|
||||||
"departtime": r.FormValue("departtime"),
|
|
||||||
"time": r.FormValue("time"),
|
|
||||||
|
|
||||||
"planDays": planDays,
|
|
||||||
"recurrent": r.FormValue("recurrent"),
|
|
||||||
"pontuelle": r.FormValue("ponctuelle"),
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request_organization := &groupsmanagement.AddGroupRequest{
|
|
||||||
Group: &groupsmanagement.Group{
|
|
||||||
Id: groupidd,
|
|
||||||
Namespace: "parcoursmob_groups_covoiturage",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.AddGroup(context.TODO(), request_organization)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", request_organization.Group.ToStorageType().ID), http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
accountsBeneficaire, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.CreateGroup(w, r, Depart, Arrive, searched, beneficiary, accountsBeneficaire, departurgeo, dstinationgeo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DisplayGroupCovoiturage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
|
|
||||||
request := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: groupid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts := []any{}
|
|
||||||
|
|
||||||
requesst := &mobilityaccounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: resp.Group.Members,
|
|
||||||
}
|
|
||||||
|
|
||||||
ressp, _ := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), requesst)
|
|
||||||
|
|
||||||
for _, account := range ressp.Accounts {
|
|
||||||
if filterAcc(r, account) {
|
|
||||||
a := account.ToStorageType()
|
|
||||||
accounts = append(accounts, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
var beneficiary any
|
|
||||||
searched := false
|
|
||||||
|
|
||||||
if r.FormValue("beneficiaryid") != "" {
|
|
||||||
|
|
||||||
searched = true
|
|
||||||
|
|
||||||
requestbeneficiary := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: r.FormValue("beneficiaryid"),
|
|
||||||
}
|
|
||||||
|
|
||||||
respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary = respbeneficiary.Account.ToStorageType()
|
|
||||||
|
|
||||||
subscribe := &groupsmanagement.SubscribeRequest{
|
|
||||||
Groupid: resp.Group.ToStorageType().ID,
|
|
||||||
Memberid: respbeneficiary.Account.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.Subscribe(context.TODO(), subscribe)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
/*******************Code to store more information about mermbers groupscovoiturage**************/
|
|
||||||
if r.FormValue("departure") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.FormValue("departure")), &a)
|
|
||||||
|
|
||||||
Depart = a
|
|
||||||
}
|
|
||||||
if r.FormValue("destination") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.FormValue("destination")), &a)
|
|
||||||
|
|
||||||
Arrive = a
|
|
||||||
}
|
|
||||||
r.ParseForm()
|
|
||||||
dataMap := map[string]any{
|
|
||||||
"depart": Depart,
|
|
||||||
"arrive": Arrive,
|
|
||||||
}
|
|
||||||
id := uuid.NewString()
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request_organizatio := &groupsmanagement.AddGroupMemberRequest{
|
|
||||||
Group: &groupsmanagement.GroupMember{
|
|
||||||
Id: id,
|
|
||||||
Memberid: respbeneficiary.Account.Id,
|
|
||||||
Groupid: resp.Group.ToStorageType().ID,
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.GroupsManagement.AddGroupMember(context.TODO(), request_organizatio)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", resp.Group.ToStorageType().ID), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//////////find all groups to store the adresse passenger///////
|
|
||||||
// grp := &groupsmanagement.GetGroupsBatchMemberRequest{
|
|
||||||
|
|
||||||
// Groupids: []string{resp.Group.ToStorageType().ID},
|
|
||||||
// }
|
|
||||||
// s, err := h.services.GRPC.GroupsManagement.GetGroupsBatchMember(context.TODO(), grp)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Error().Err(err).Msg("")
|
|
||||||
// w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// groups := map[string]any{}
|
|
||||||
|
|
||||||
// if err == nil {
|
|
||||||
// for _, g := range s.Groups {
|
|
||||||
// groups[g.Memberid] = g.ToStorageType()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//////////find all groups to store the adresse passenger///////
|
|
||||||
///////////try to optimise the code ////////////////////////////
|
|
||||||
groups, _ := h.services.GetGroupsMemberMap(resp.Group.ToStorageType().ID)
|
|
||||||
var number string = strconv.Itoa(len(resp.Group.Members))
|
|
||||||
accountsBeneficaire, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.DisplayGroupCovoiturage(w, r, number, resp.Group.ToStorageType().ID, Depart, Arrive, accounts, cacheid, searched, beneficiary, resp.Group.ToStorageType(), accountsBeneficaire, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) UpdateGroupCovoiturage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id := vars["id"]
|
|
||||||
groupid := vars["groupid"]
|
|
||||||
memberid := vars["memberid"]
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
//////////get groupid covoiturage//////////
|
|
||||||
request := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: groupid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////get group member////////////////////////////////
|
|
||||||
|
|
||||||
reequest := &groupsmanagement.GetGroupMemberRequest{
|
|
||||||
Id: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
ressp, err := h.services.GRPC.GroupsManagement.GetGroupMember(context.TODO(), reequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req := &groupsmanagement.UnsubscribeMemberRequest{
|
|
||||||
Id: ressp.Group.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, errr := h.services.GRPC.GroupsManagement.UnsubscribeMember(context.TODO(), req)
|
|
||||||
if errr != nil {
|
|
||||||
log.Error().Err(errr).Msg("Issue in groups management service - InsubscribeMember")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
members := resp.Group.Members
|
|
||||||
for i := 0; i < len(members); i++ {
|
|
||||||
if members[i] == memberid {
|
|
||||||
members = append(members[:i], members[(i+1):]...)
|
|
||||||
resp.Group.Members = members
|
|
||||||
reequest := &groupsmanagement.UnsubscribeRequest{
|
|
||||||
Groupid: resp.Group.ToStorageType().ID,
|
|
||||||
Memberid: memberid,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.GroupsManagement.Unsubscribe(context.TODO(), reequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/journeys/groups_covoiturage/create/%s", groupid), http.StatusFound)
|
|
||||||
/*
|
|
||||||
I must add "return" to resolve the err
|
|
||||||
http: superfluous response.WriteHeader call from git.coopgo.io/coopgo-apps/parcoursmob/renderer.(*Renderer).Render (renderer.go:50)
|
|
||||||
*/
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.UpdateGroupCovoiturage(w, r, groupid, memberid)
|
|
||||||
}
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserForm struct {
|
|
||||||
FirstName string `json:"first_name" validate:"required"`
|
|
||||||
LastName string `json:"last_name" validate:"required"`
|
|
||||||
Email string `json:"email" validate:"required,email"`
|
|
||||||
PhoneNumber string `json:"phone_number" `
|
|
||||||
Address any `json:"address,omitempty"`
|
|
||||||
Gender string `json:"gender"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) MemberDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
adm := strings.Split(r.URL.Path, "/")
|
|
||||||
adminid := adm[3]
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: adminid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//////////////////////////////////add organisations/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
var allIds []string
|
|
||||||
for _, v := range resp.Account.ToStorageType().Data["groups"].([]any) {
|
|
||||||
s := fmt.Sprintf("%v", v)
|
|
||||||
if !(strings.Contains(s, "admin")) {
|
|
||||||
allIds = append(allIds, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reques := &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: allIds,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), reques)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupsName []string
|
|
||||||
|
|
||||||
for _, group := range res.Groups {
|
|
||||||
g := fmt.Sprintf("%v", group.ToStorageType().Data["name"])
|
|
||||||
groupsName = append(groupsName, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.MemberDisplay(w, r, resp.Account.ToStorageType(), groupsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) MemberUpdate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
adm := strings.Split(r.URL.Path, "/")
|
|
||||||
userID := adm[3]
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
dataMap, err := parseUserForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: userID,
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/members/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: userID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.MemberUpdate(w, r, resp.Account.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUserForm(r *http.Request) (map[string]any, error) {
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
formData := UserForm{
|
|
||||||
FirstName: r.PostFormValue("first_name"),
|
|
||||||
LastName: r.PostFormValue("last_name"),
|
|
||||||
Email: r.PostFormValue("email"),
|
|
||||||
PhoneNumber: r.PostFormValue("phone_number"),
|
|
||||||
Gender: r.PostFormValue("gender"),
|
|
||||||
}
|
|
||||||
|
|
||||||
validate := formvalidators.New()
|
|
||||||
if err := validate.Struct(formData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := json.Marshal(formData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataMap map[string]any
|
|
||||||
err = json.Unmarshal(d, &dataMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) MembersList(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
accounts, err := h.services.GetAccounts()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var groupsName []string
|
|
||||||
|
|
||||||
for _, v := range accounts {
|
|
||||||
adminid := v.ID
|
|
||||||
request := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: adminid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//////////////////////////////////add organisations/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
var allIds []string
|
|
||||||
for _, v := range resp.Account.ToStorageType().Data["groups"].([]any) {
|
|
||||||
s := fmt.Sprintf("%v", v)
|
|
||||||
if !(strings.Contains(s, "admin")) {
|
|
||||||
allIds = append(allIds, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
reques := &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: allIds,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), reques)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g := ""
|
|
||||||
for _, group := range res.Groups {
|
|
||||||
g += fmt.Sprintf("%v", group.ToStorageType().Data["name"]) + " "
|
|
||||||
}
|
|
||||||
groupsName = append(groupsName, g)
|
|
||||||
|
|
||||||
}
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
|
|
||||||
|
|
||||||
h.Renderer.MembersList(w, r, accounts, cacheid, groupsName)
|
|
||||||
}
|
|
||||||
|
|
@ -1,999 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
"git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/proto"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/paulmach/orb"
|
|
||||||
"github.com/paulmach/orb/geojson"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrganizedCarpoolDriversForm 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"`
|
|
||||||
AddressDestination any `json:"address_destination,omitempty"`
|
|
||||||
Gender string `json:"gender"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolOverview(w http.ResponseWriter, r *http.Request) {
|
|
||||||
accounts, err := h.organizedCarpoolDrivers(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("issue getting solidarity drivers")
|
|
||||||
accounts = []mobilityaccountsstorage.Account{}
|
|
||||||
}
|
|
||||||
|
|
||||||
accountsMap := map[string]mobilityaccountsstorage.Account{}
|
|
||||||
|
|
||||||
for _, a := range accounts {
|
|
||||||
accountsMap[a.ID] = a
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiariesMap, err := h.services.GetBeneficiariesMap()
|
|
||||||
if err != nil {
|
|
||||||
beneficiariesMap = map[string]mobilityaccountsstorage.Account{}
|
|
||||||
}
|
|
||||||
|
|
||||||
bookingsproto, err := h.services.GRPC.CarpoolService.GetCarpoolBookings(context.Background(), &proto.GetCarpoolBookingsRequest{
|
|
||||||
MinDate: timestamppb.Now(),
|
|
||||||
MaxDate: timestamppb.New(time.Now().Add(24 * 365 * time.Hour)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("issue retreving bookings")
|
|
||||||
}
|
|
||||||
|
|
||||||
bookings := []*proto.CarpoolServiceBooking{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, b := range bookingsproto.Bookings {
|
|
||||||
bookings = append(bookings, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.SortFunc(accounts, func(a, b mobilityaccountsstorage.Account) int {
|
|
||||||
return strings.Compare(
|
|
||||||
strings.ToLower(fmt.Sprintf("%s %s", a.Data["first_name"].(string), a.Data["last_name"].(string))),
|
|
||||||
strings.ToLower(fmt.Sprintf("%s %s", b.Data["first_name"].(string), b.Data["last_name"].(string))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
slices.SortFunc(bookings, func(a, b *proto.CarpoolServiceBooking) int {
|
|
||||||
return cmp.Compare(a.PassengerPickupDate.AsTime().Unix(), b.PassengerPickupDate.AsTime().Unix())
|
|
||||||
})
|
|
||||||
|
|
||||||
h.Renderer.OrganizedCarpoolOverview(w, r, accounts, accountsMap, beneficiariesMap, bookings)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolBookingDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingId := vars["bookingid"]
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.CarpoolService.GetBooking(context.Background(), &proto.GetCarpoolBookingRequest{
|
|
||||||
BookingId: bookingId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not get carpool booking")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Booking == nil {
|
|
||||||
log.Error().Msg("carpool booking not found")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := h.services.GetAccount(resp.Booking.Driver.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("driver retrieval issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
passenger, err := h.services.GetAccount(resp.Booking.Passenger.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("passenger retrieval issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract driver departure and arrival addresses from DriverRoute GeoJSON
|
|
||||||
var driverDepartureAddress, driverArrivalAddress string
|
|
||||||
if resp.Booking.DriverRoute != nil && resp.Booking.DriverRoute.Serialized != "" {
|
|
||||||
fc, err := geojson.UnmarshalFeatureCollection([]byte(resp.Booking.DriverRoute.Serialized))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not unmarshal driver route geojson")
|
|
||||||
} else {
|
|
||||||
// Extract departure address (first feature)
|
|
||||||
if len(fc.Features) > 0 {
|
|
||||||
if addr, ok := fc.Features[0].Properties["label"]; ok {
|
|
||||||
if addrStr, ok := addr.(string); ok {
|
|
||||||
driverDepartureAddress = addrStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Extract arrival address (last feature)
|
|
||||||
if len(fc.Features) > 1 {
|
|
||||||
if addr, ok := fc.Features[1].Properties["label"]; ok {
|
|
||||||
if addrStr, ok := addr.(string); ok {
|
|
||||||
driverArrivalAddress = addrStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.OrganizedCarpoolBookingDisplay(w, r, resp.Booking, driver, passenger, driverDepartureAddress, driverArrivalAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolBookingStatus(action string) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingId := vars["bookingid"]
|
|
||||||
|
|
||||||
var status proto.CarpoolServiceBookingStatus
|
|
||||||
if action == "confirm" {
|
|
||||||
status = proto.CarpoolServiceBookingStatus_CONFIRMED
|
|
||||||
} else if action == "cancel" {
|
|
||||||
status = proto.CarpoolServiceBookingStatus_CANCELLED
|
|
||||||
} else if action == "waitconfirmation" {
|
|
||||||
status = proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION
|
|
||||||
} else {
|
|
||||||
log.Error().Str("action", action).Msg("unknown booking action")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.CarpoolService.UpdateBooking(context.Background(), &proto.UpdateCarpoolBookingRequest{
|
|
||||||
BookingId: bookingId,
|
|
||||||
Status: status,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("update carpool booking status issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect back to the booking display page
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/bookings/%s", bookingId), http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) organizedCarpoolDrivers(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
|
|
||||||
accounts := []mobilityaccountsstorage.Account{}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.GetAccountsRequest{
|
|
||||||
Namespaces: []string{"organized_carpool_drivers"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
return accounts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range resp.Accounts {
|
|
||||||
if filterAccount(r, account) {
|
|
||||||
a := account.ToStorageType()
|
|
||||||
accounts = append(accounts, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return accounts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolCreateDriver(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
dataMap, err := parseOrganizedCarpoolDriversForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.RegisterRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Namespace: "organized_carpool_drivers",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.OrganizedCarpoolCreateDriver(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolUpdateDriver(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
|
|
||||||
driver, err := h.services.GetAccount(driverID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue retrieving driver account")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
dataMap, err := parseOrganizedCarpoolDriversUpdateForm(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: driverID,
|
|
||||||
Namespace: "organized_carpool_drivers",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.OrganizedCarpoolUpdateDriver(w, r, driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolDriverDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
|
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS + "/" + driverID)
|
|
||||||
|
|
||||||
driver, err := h.services.GetAccount(driverID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Issue retrieving driver account")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Any("driver data", driver.Data).Msg("driver retrieved")
|
|
||||||
|
|
||||||
trips := []*geojson.FeatureCollection{}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.CarpoolService.GetRegularRoutes(context.Background(), &proto.GetRegularRoutesRequest{
|
|
||||||
UserId: driverID,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, r := range resp.Routes {
|
|
||||||
t, err := geojson.UnmarshalFeatureCollection([]byte(r.Serialized))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not unmarshall feature collection")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
trips = append(trips, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.OrganizedCarpoolDriverDisplay(w, r, driver, trips, documents)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolArchiveDriver(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
|
|
||||||
data, _ := structpb.NewValue(map[string]any{
|
|
||||||
"archived": true,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: driverID,
|
|
||||||
Namespace: "organized_carpool_drivers",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolUnarchiveDriver(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
|
|
||||||
data, _ := structpb.NewValue(map[string]any{
|
|
||||||
"archived": false,
|
|
||||||
})
|
|
||||||
|
|
||||||
request := &mobilityaccounts.UpdateDataRequest{
|
|
||||||
Account: &mobilityaccounts.Account{
|
|
||||||
Id: driverID,
|
|
||||||
Namespace: "organized_carpool_drivers",
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.MobilityAccounts.UpdateData(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", resp.Account.Id), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolDriverDocuments(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
|
|
||||||
// r.ParseForm()
|
|
||||||
r.ParseMultipartForm(100 * 1024 * 1024)
|
|
||||||
|
|
||||||
document_type := r.FormValue("type")
|
|
||||||
document_name := r.FormValue("name")
|
|
||||||
|
|
||||||
file, header, err := r.FormFile("file-upload")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"type": document_type,
|
|
||||||
"name": document_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Any("metadata", metadata).Msg("Metadata")
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS, fmt.Sprintf("%s/%s_%s", driverID, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
document := vars["document"]
|
|
||||||
|
|
||||||
file, info, err := h.filestorage.Get(filestorage.PREFIX_ORGANIZED_CARPOOL_DRIVERS, fmt.Sprintf("%s/%s", driverID, document))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", info.ContentType)
|
|
||||||
if _, err = io.Copy(w, file); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s", driverID), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolAddTrip(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "POST" {
|
|
||||||
log.Error().Msg("Wrong method")
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("error parsong availabilities form")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
|
|
||||||
trips := []*proto.CarpoolFeatureCollection{}
|
|
||||||
|
|
||||||
outwardtime := r.PostFormValue("outwardtime")
|
|
||||||
returntime := r.PostFormValue("returntime")
|
|
||||||
dep := r.PostFormValue("address_departure")
|
|
||||||
dest := r.PostFormValue("address_destination")
|
|
||||||
|
|
||||||
departure, err := geojson.UnmarshalFeature([]byte(dep))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed parsong departure geojson")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
destination, err := geojson.UnmarshalFeature([]byte(dest))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed parsong departure geojson")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
outwardroute, err := h.services.Routing.Route([]orb.Point{departure.Point(), destination.Point()})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed calling route search")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
returnroute, err := h.services.Routing.Route([]orb.Point{destination.Point(), departure.Point()})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed calling route search")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
outwardschedules := []map[string]any{}
|
|
||||||
returnschedules := []map[string]any{}
|
|
||||||
|
|
||||||
if r.PostFormValue("days.monday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "MON",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "MON",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if r.PostFormValue("days.tuesday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "TUE",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "TUE",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if r.PostFormValue("days.wednesday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "WED",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "WED",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if r.PostFormValue("days.thursday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "THU",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "THU",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if r.PostFormValue("days.friday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "FRI",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "FRI",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if r.PostFormValue("days.saturday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "SAT",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "SAT",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if r.PostFormValue("days.sunday") == "on" {
|
|
||||||
outwardschedules = append(outwardschedules, map[string]any{
|
|
||||||
"day": "SUN",
|
|
||||||
"time_of_day": outwardtime,
|
|
||||||
})
|
|
||||||
returnschedules = append(returnschedules, map[string]any{
|
|
||||||
"day": "SUN",
|
|
||||||
"time_of_day": returntime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
outward_fc := geojson.NewFeatureCollection()
|
|
||||||
outward_fc.Append(departure)
|
|
||||||
outward_fc.Append(destination)
|
|
||||||
outward_fc.ExtraMembers = geojson.Properties{}
|
|
||||||
outward_fc.ExtraMembers["properties"] = map[string]any{
|
|
||||||
"is_driver": true,
|
|
||||||
"is_passenger": false,
|
|
||||||
"user": mobilityaccountsstorage.Account{
|
|
||||||
ID: driverID,
|
|
||||||
},
|
|
||||||
"polyline": outwardroute.Summary.Polyline,
|
|
||||||
"schedules": outwardschedules,
|
|
||||||
"driver_options": map[string]any{},
|
|
||||||
"passenger_options": map[string]any{},
|
|
||||||
}
|
|
||||||
outwardtrip, err := outward_fc.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed parsong return geojson")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return_fc := geojson.NewFeatureCollection()
|
|
||||||
return_fc.Append(destination)
|
|
||||||
return_fc.Append(departure)
|
|
||||||
return_fc.ExtraMembers = geojson.Properties{}
|
|
||||||
return_fc.ExtraMembers["properties"] = map[string]any{
|
|
||||||
"is_driver": true,
|
|
||||||
"is_passenger": false,
|
|
||||||
"user": mobilityaccountsstorage.Account{
|
|
||||||
ID: driverID,
|
|
||||||
},
|
|
||||||
"polyline": returnroute.Summary.Polyline,
|
|
||||||
"schedules": returnschedules,
|
|
||||||
"driver_options": map[string]any{},
|
|
||||||
"passenger_options": map[string]any{},
|
|
||||||
}
|
|
||||||
returntrip, err := return_fc.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed parsong return geojson")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
trips = append(trips, &proto.CarpoolFeatureCollection{
|
|
||||||
Serialized: string(outwardtrip),
|
|
||||||
})
|
|
||||||
trips = append(trips, &proto.CarpoolFeatureCollection{
|
|
||||||
Serialized: string(returntrip),
|
|
||||||
})
|
|
||||||
|
|
||||||
req := &proto.CreateRegularRoutesRequest{
|
|
||||||
Routes: trips,
|
|
||||||
}
|
|
||||||
_, err = h.services.GRPC.CarpoolService.CreateRegularRoutes(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not create regular routes")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Msg("Finished creating carpool routes")
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolDeleteTrip(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverID := vars["driverid"]
|
|
||||||
tripID := vars["tripid"]
|
|
||||||
|
|
||||||
req := &proto.DeleteRegularRoutesRequest{
|
|
||||||
Ids: []string{tripID},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.CarpoolService.DeleteRegularRoutes(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not delete regular routes")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/drivers/%s", driverID), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) OrganizedCarpoolJourney(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
driverId := vars["driverid"]
|
|
||||||
journeyId := vars["journeyid"]
|
|
||||||
|
|
||||||
var passenger mobilityaccountsstorage.Account
|
|
||||||
passengerId := r.URL.Query().Get("passengerid")
|
|
||||||
log.Info().Str("journeyid", journeyId).Str("driverid", driverId).Str("passengerid", passengerId).Msg("organized carpool journey")
|
|
||||||
|
|
||||||
// Get the planned trip data
|
|
||||||
journeyResp, err := h.services.GRPC.CarpoolService.GetPlannedTrip(context.Background(), &proto.GetPlannedTripRequest{
|
|
||||||
Id: journeyId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not get carpool journey")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
journey, err := geojson.UnmarshalFeatureCollection([]byte(journeyResp.PlannedTrip.Serialized))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not unmarshal carpool journey")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
departureDate := journey.ExtraMembers["departure_date"]
|
|
||||||
var departureTime *timestamppb.Timestamp
|
|
||||||
if departureDate != nil {
|
|
||||||
if dd, ok := departureDate.(string); ok {
|
|
||||||
dt, _ := time.Parse("2006-01-02 15:04", dd)
|
|
||||||
departureTime = timestamppb.New(dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract operator from journey data
|
|
||||||
var operatorID string
|
|
||||||
if operator, ok := journey.ExtraMembers["operator"]; ok {
|
|
||||||
if op, ok := operator.(string); ok {
|
|
||||||
operatorID = op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if operatorID == "" {
|
|
||||||
operatorID = "example.coopgo.fr" // fallback to default
|
|
||||||
}
|
|
||||||
|
|
||||||
if departureTime == nil {
|
|
||||||
// Fallback to current time if we can't extract departure time
|
|
||||||
departureTime = timestamppb.New(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
if passengerId == "" {
|
|
||||||
log.Error().Msg("missing passenger ID for carpool booking")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not parse form input")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract journey properties from the geojson data
|
|
||||||
var journeyProps map[string]any
|
|
||||||
if props, ok := journey.ExtraMembers["properties"]; ok {
|
|
||||||
if propsMap, ok := props.(map[string]any); ok {
|
|
||||||
journeyProps = propsMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if journeyProps == nil {
|
|
||||||
log.Error().Msg("could not extract journey properties")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract departure date from journey ExtraMembers
|
|
||||||
if depDate, ok := journey.ExtraMembers["departure_date"]; ok {
|
|
||||||
if depDateStr, ok := depDate.(string); ok {
|
|
||||||
if parsedTime, err := time.Parse("2006-01-02T15:04:05Z", depDateStr); err == nil {
|
|
||||||
departureTime = timestamppb.New(parsedTime)
|
|
||||||
} else if parsedTime, err := time.Parse("2006-01-02", depDateStr); err == nil {
|
|
||||||
departureTime = timestamppb.New(parsedTime)
|
|
||||||
} else {
|
|
||||||
log.Warn().Str("departure_date", depDateStr).Msg("could not parse departure date, using current time")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract passenger pickup/drop coordinates and addresses from stored passenger data
|
|
||||||
var pickupLat, pickupLng, dropLat, dropLng float64
|
|
||||||
var pickupAddress, dropAddress string
|
|
||||||
|
|
||||||
// Check if we have passenger pickup and drop features in the journey's extra members
|
|
||||||
if pickupData, ok := journey.ExtraMembers["passenger_pickup"]; ok {
|
|
||||||
if pickupMap, ok := pickupData.(map[string]interface{}); ok {
|
|
||||||
if geometry, ok := pickupMap["geometry"].(map[string]interface{}); ok {
|
|
||||||
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
|
|
||||||
if lng, ok := coords[0].(float64); ok {
|
|
||||||
pickupLng = lng
|
|
||||||
}
|
|
||||||
if lat, ok := coords[1].(float64); ok {
|
|
||||||
pickupLat = lat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if properties, ok := pickupMap["properties"].(map[string]interface{}); ok {
|
|
||||||
if label, ok := properties["label"].(string); ok {
|
|
||||||
pickupAddress = label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dropData, ok := journey.ExtraMembers["passenger_drop"]; ok {
|
|
||||||
if dropMap, ok := dropData.(map[string]interface{}); ok {
|
|
||||||
if geometry, ok := dropMap["geometry"].(map[string]interface{}); ok {
|
|
||||||
if coords, ok := geometry["coordinates"].([]interface{}); ok && len(coords) >= 2 {
|
|
||||||
if lng, ok := coords[0].(float64); ok {
|
|
||||||
dropLng = lng
|
|
||||||
}
|
|
||||||
if lat, ok := coords[1].(float64); ok {
|
|
||||||
dropLat = lat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if properties, ok := dropMap["properties"].(map[string]interface{}); ok {
|
|
||||||
if label, ok := properties["label"].(string); ok {
|
|
||||||
dropAddress = label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Str("pickup_address", pickupAddress).Str("drop_address", dropAddress).Msg("Extracted addresses")
|
|
||||||
|
|
||||||
// Extract time from schedules if available and no specific departure_date was found
|
|
||||||
if departureTime.AsTime().Equal(time.Now().Truncate(time.Second)) {
|
|
||||||
if schedules, ok := journeyProps["schedules"]; ok {
|
|
||||||
if schedulesList, ok := schedules.([]any); ok && len(schedulesList) > 0 {
|
|
||||||
if schedule, ok := schedulesList[0].(map[string]any); ok {
|
|
||||||
if timeOfDay, ok := schedule["time_of_day"].(string); ok {
|
|
||||||
// Parse time and combine with current date
|
|
||||||
now := time.Now()
|
|
||||||
timeStr := fmt.Sprintf("%s %s", now.Format("2006-01-02"), timeOfDay)
|
|
||||||
if depTime, err := time.Parse("2006-01-02 15:04", timeStr); err == nil {
|
|
||||||
departureTime = timestamppb.New(depTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
motivation := r.PostFormValue("motivation")
|
|
||||||
|
|
||||||
// Create carpool booking using extracted journey data
|
|
||||||
booking := &proto.CarpoolServiceBooking{
|
|
||||||
Id: uuid.NewString(),
|
|
||||||
Driver: &proto.CarpoolServiceUser{
|
|
||||||
Id: driverId,
|
|
||||||
Operator: operatorID,
|
|
||||||
},
|
|
||||||
Passenger: &proto.CarpoolServiceUser{
|
|
||||||
Id: passengerId,
|
|
||||||
Operator: operatorID,
|
|
||||||
},
|
|
||||||
PassengerPickupDate: departureTime,
|
|
||||||
PassengerPickupLat: pickupLat,
|
|
||||||
PassengerPickupLng: pickupLng,
|
|
||||||
PassengerDropLat: dropLat,
|
|
||||||
PassengerDropLng: dropLng,
|
|
||||||
PassengerPickupAddress: &pickupAddress,
|
|
||||||
PassengerDropAddress: &dropAddress,
|
|
||||||
Status: proto.CarpoolServiceBookingStatus_WAITING_DRIVER_CONFIRMATION,
|
|
||||||
DriverJourneyId: journeyId,
|
|
||||||
}
|
|
||||||
|
|
||||||
bookingRes, err := h.services.GRPC.CarpoolService.CreateBooking(context.Background(), &proto.CreateCarpoolBookingRequest{
|
|
||||||
Booking: booking,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("cannot create carpool booking")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("booking_id", bookingRes.Booking.Id).Msg("Carpool booking created successfully")
|
|
||||||
|
|
||||||
// Send SMS notification if requested
|
|
||||||
message := r.PostFormValue("message")
|
|
||||||
doNotSend := r.PostFormValue("do_not_send")
|
|
||||||
if message != "" && doNotSend != "on" {
|
|
||||||
send_message := strings.ReplaceAll(message, "{booking_id}", bookingRes.Booking.Id)
|
|
||||||
send_message = strings.ReplaceAll(send_message, "{motivation}", motivation)
|
|
||||||
log.Debug().Str("message", send_message).Msg("Carpool booking created: sending message")
|
|
||||||
h.GenerateSMS(driverId, send_message)
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/organized-carpool/"), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := h.services.GetAccount(driverId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not get driver")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if passengerId != "" {
|
|
||||||
passenger, err = h.services.GetAccount(passengerId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not get account")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.OrganizedCarpoolJourney(w, r, journey, driver, passenger, beneficiaries)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOrganizedCarpoolDriversForm(r *http.Request) (map[string]any, error) {
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Any("form content", r.Form).Msg("parsing form")
|
|
||||||
|
|
||||||
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 := OrganizedCarpoolDriversForm{
|
|
||||||
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("address_destination") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.PostFormValue("address_destination")), &a)
|
|
||||||
|
|
||||||
formData.AddressDestination = a
|
|
||||||
}
|
|
||||||
|
|
||||||
validate := formvalidators.New()
|
|
||||||
if err := validate.Struct(formData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := json.Marshal(formData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataMap map[string]any
|
|
||||||
err = json.Unmarshal(d, &dataMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOrganizedCarpoolDriversUpdateForm(r *http.Request) (map[string]any, error) {
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Any("form content", r.Form).Msg("parsing update form")
|
|
||||||
|
|
||||||
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 := OrganizedCarpoolDriversForm{
|
|
||||||
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("address_destination") != "" {
|
|
||||||
var a any
|
|
||||||
json.Unmarshal([]byte(r.PostFormValue("address_destination")), &a)
|
|
||||||
|
|
||||||
formData.AddressDestination = a
|
|
||||||
}
|
|
||||||
|
|
||||||
validate := formvalidators.New()
|
|
||||||
if err := validate.Struct(formData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := json.Marshal(formData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataMap map[string]any
|
|
||||||
err = json.Unmarshal(d, &dataMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle other_properties for update form
|
|
||||||
if r.PostFormValue("other_properties") != "" {
|
|
||||||
var otherProps map[string]any
|
|
||||||
if err := json.Unmarshal([]byte(r.PostFormValue("other_properties")), &otherProps); err == nil {
|
|
||||||
if dataMap["other_properties"] == nil {
|
|
||||||
dataMap["other_properties"] = make(map[string]any)
|
|
||||||
}
|
|
||||||
for k, v := range otherProps {
|
|
||||||
dataMap["other_properties"].(map[string]any)[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataMap, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
|
|
||||||
"git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/transformers"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) SolidarityTransportExternalBookingProposal(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingId := vars["bookingid"]
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.SolidarityTransport.GetSolidarityTransportBooking(context.Background(), &gen.GetSolidarityTransportBookingRequest{
|
|
||||||
Id: bookingId,
|
|
||||||
})
|
|
||||||
|
|
||||||
booking, err := transformers.BookingProtoToType(resp.Booking)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not transform booking type")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := h.services.GetAccount(booking.DriverId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("driver retrieval issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
passenger, err := h.services.GetAccount(booking.PassengerId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("passenger retrieval issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
if err = r.ParseForm(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("error parsing form data")
|
|
||||||
}
|
|
||||||
message := r.FormValue("message")
|
|
||||||
action := r.FormValue("action")
|
|
||||||
var status string
|
|
||||||
if action == "confirm" {
|
|
||||||
status = "VALIDATED"
|
|
||||||
} else if action == "cancel" {
|
|
||||||
status = "CANCELLED"
|
|
||||||
} else if action == "waitconfirmation" {
|
|
||||||
status = "WAITING_CONFIRMATION"
|
|
||||||
}
|
|
||||||
if status != "" {
|
|
||||||
if _, err := h.services.GRPC.SolidarityTransport.UpdateSolidarityTransportBookingStatus(context.Background(), &gen.UpdateSolidarityTransportBookingStatusRequest{
|
|
||||||
BookingId: bookingId,
|
|
||||||
NewStatus: status,
|
|
||||||
Reason: "Refusé par le bénévole",
|
|
||||||
}); err != nil {
|
|
||||||
log.Error().Err(err).Msg("update booking status issue")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
booking.Status = status
|
|
||||||
if status == "VALIDATED" {
|
|
||||||
h.GenerateSMS(passenger.ID, message)
|
|
||||||
if err := h.creditWallet(driver.ID, booking.Journey.Price.Amount, "Transport solidaire", "Crédit transport solidaire"); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not credit driver wallet")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if status == "CANCELLED" {
|
|
||||||
if err := h.creditWallet(passenger.ID, booking.Journey.Price.Amount, "Transport solidaire", "Remboursement annulation transport solidaire"); err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not credit wallet")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// NOTIFY GROUP MEMBERS
|
|
||||||
groupsrequest := &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_organizations"},
|
|
||||||
Member: booking.PassengerId,
|
|
||||||
}
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), groupsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
if len(groupsresp.Groups) > 0 {
|
|
||||||
members, _, err := h.groupmembers(groupsresp.Groups[0].Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not retrieve groupe members")
|
|
||||||
} else {
|
|
||||||
for _, m := range members {
|
|
||||||
if email, ok := m.Data["email"].(string); ok {
|
|
||||||
h.emailing.Send("solidarity_transport.booking_driver_decline", email, map[string]string{
|
|
||||||
"bookingid": booking.Id,
|
|
||||||
"baseUrl": h.config.GetString("base_url"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.SolidarityTransportExternalBookingDisplay(w, r, booking, driver, passenger)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,44 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) SupportSend(w http.ResponseWriter, r *http.Request) {
|
|
||||||
c := r.Context().Value(identification.ClaimsKey)
|
|
||||||
if c == nil {
|
|
||||||
log.Error().Msg("no current user claims")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current_user_claims := c.(map[string]any)
|
|
||||||
|
|
||||||
comment := r.PostFormValue(("comment"))
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
user_email := current_user_claims["email"].(string)
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"key": comment,
|
|
||||||
"user": user_email,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Str("user_email", user_email).Msg("Sending message")
|
|
||||||
|
|
||||||
if err := h.emailing.Send("support.request", "support@parcoursmob.fr", data); err != nil {
|
|
||||||
log.Error().Err(err).Msg("error sending email")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.SupportSend(w, r, comment, current_user_claims)
|
|
||||||
}
|
|
||||||
|
|
@ -1,710 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get Vehicles
|
|
||||||
request := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles := []fleetsstorage.Vehicle{}
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
vehicles_map := map[string]fleetsstorage.Vehicle{}
|
|
||||||
|
|
||||||
for _, vehicle := range resp.Vehicles {
|
|
||||||
if filterVehicle(r, vehicle) {
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
vehicleBookings := []fleetsstorage.Booking{}
|
|
||||||
for _, b := range v.Bookings {
|
|
||||||
if b.Status() != fleetsstorage.StatusOld {
|
|
||||||
if deleted, ok := b.Data["Deleted"].(bool); !ok && !deleted {
|
|
||||||
bookings = append(bookings, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b.Unavailableto.After(time.Now()) {
|
|
||||||
vehicleBookings = append(vehicleBookings, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.Bookings = vehicleBookings
|
|
||||||
vehicles = append(vehicles, v)
|
|
||||||
vehicles_map[v.ID] = v
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
driversMap, _ := h.services.GetBeneficiariesMap()
|
|
||||||
|
|
||||||
sort.Sort(sorting.VehiclesByLicencePlate(vehicles))
|
|
||||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
||||||
h.Renderer.VehiclesManagementOverview(w, r, vehicles, vehicles_map, driversMap, bookings)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get Vehicles
|
|
||||||
request := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
vehicles_map := map[string]fleetsstorage.Vehicle{}
|
|
||||||
|
|
||||||
for _, vehicle := range resp.Vehicles {
|
|
||||||
if filterVehicle(r, vehicle) {
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
vehicles_map[v.ID] = v
|
|
||||||
// bookings = append(bookings, v.Bookings...)
|
|
||||||
for _, b := range v.Bookings {
|
|
||||||
if v, ok := b.Data["administrator_unavailability"].(bool); !ok || !v {
|
|
||||||
bookings = append(bookings, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
||||||
|
|
||||||
cacheid := uuid.NewString()
|
|
||||||
h.cache.PutWithTTL(cacheid, bookings, 1*time.Hour)
|
|
||||||
|
|
||||||
driversMap, _ := h.services.GetBeneficiariesMap()
|
|
||||||
|
|
||||||
h.Renderer.VehiclesManagementBookingsList(w, r, vehicles_map, driversMap, bookings, cacheid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehiclesFleetAdd(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dataMap := map[string]any{}
|
|
||||||
if v := r.FormValue("name"); v != "" {
|
|
||||||
dataMap["name"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("address"); v != "" {
|
|
||||||
var address map[string]any
|
|
||||||
err := json.Unmarshal([]byte(v), &address)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dataMap["address"] = address
|
|
||||||
}
|
|
||||||
if v := r.FormValue("informations"); v != "" {
|
|
||||||
dataMap["informations"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("licence_plate"); v != "" {
|
|
||||||
dataMap["licence_plate"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("automatic"); v != "" {
|
|
||||||
dataMap["automatic"] = (v == "on")
|
|
||||||
}
|
|
||||||
if v := r.FormValue("kilometers"); v != "" {
|
|
||||||
dataMap["kilometers"] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicle := &fleets.Vehicle{
|
|
||||||
Id: uuid.NewString(),
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
Type: r.FormValue("type"),
|
|
||||||
Administrators: []string{group.ID},
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &fleets.AddVehicleRequest{
|
|
||||||
Vehicle: vehicle,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.Fleets.AddVehicle(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", vehicle.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles_types := h.config.GetStringSlice("modules.fleets.vehicle_types")
|
|
||||||
h.Renderer.VehiclesFleetAdd(w, r, vehicles_types)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehiclesFleetDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
vehicleid := vars["vehicleid"]
|
|
||||||
|
|
||||||
request := &fleets.GetVehicleRequest{
|
|
||||||
Vehicleid: vehicleid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetVehicle(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.services.GetBeneficiariesMap()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("issue retrieving beneficiaries")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
/*diag := []diagsstorage.Diag{}
|
|
||||||
|
|
||||||
diagsrequest := &diags.GetDiagsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_vehicles"},
|
|
||||||
}
|
|
||||||
|
|
||||||
diagsresp, err := h.services.GRPC.Diags.GetDiags(context.TODO(), diagsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("did not retrieve diags")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range diagsresp.Diags {
|
|
||||||
diagData := d.Data.AsMap()
|
|
||||||
if vehicle, ok := diagData["vehicle"].(string); ok && vehicle == vehicleid {
|
|
||||||
diag = append(diag, d.ToStorageType())
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
diagsAny := []any{}
|
|
||||||
/*for i, d := range diag {
|
|
||||||
diagsAny[i] = d
|
|
||||||
}*/
|
|
||||||
|
|
||||||
h.Renderer.VehiclesFleetDisplay(w, r, resp.Vehicle.ToStorageType(), beneficiaries, diagsAny)
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterVehicle(r *http.Request, v *fleets.Vehicle) bool {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(storage.Group)
|
|
||||||
|
|
||||||
for _, n := range v.Administrators {
|
|
||||||
if n == group.ID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingid := vars["bookingid"]
|
|
||||||
|
|
||||||
booking, err := h.services.GetBooking(bookingid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
newbooking, _ := fleets.BookingFromStorageType(&booking)
|
|
||||||
|
|
||||||
startdate := r.FormValue("startdate")
|
|
||||||
if startdate != "" {
|
|
||||||
newstartdate, _ := time.Parse("2006-01-02", startdate)
|
|
||||||
newbooking.Startdate = timestamppb.New(newstartdate)
|
|
||||||
|
|
||||||
if newstartdate.Before(newbooking.Unavailablefrom.AsTime()) {
|
|
||||||
newbooking.Unavailablefrom = timestamppb.New(newstartdate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enddate := r.FormValue("enddate")
|
|
||||||
if enddate != "" {
|
|
||||||
newenddate, _ := time.Parse("2006-01-02", enddate)
|
|
||||||
newbooking.Enddate = timestamppb.New(newenddate)
|
|
||||||
|
|
||||||
if newenddate.After(newbooking.Unavailableto.AsTime()) || newenddate.Equal(newbooking.Unavailableto.AsTime()) {
|
|
||||||
newbooking.Unavailableto = timestamppb.New(newenddate.Add(24 * time.Hour))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unavailablefrom := r.FormValue("unavailablefrom")
|
|
||||||
if unavailablefrom != "" {
|
|
||||||
newunavailablefrom, _ := time.Parse("2006-01-02", unavailablefrom)
|
|
||||||
newbooking.Unavailablefrom = timestamppb.New(newunavailablefrom)
|
|
||||||
}
|
|
||||||
|
|
||||||
unavailableto := r.FormValue("unavailableto")
|
|
||||||
if unavailableto != "" {
|
|
||||||
newunavailableto, _ := time.Parse("2006-01-02", unavailableto)
|
|
||||||
newbooking.Unavailableto = timestamppb.New(newunavailableto)
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &fleets.UpdateBookingRequest{
|
|
||||||
Booking: newbooking,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.Fleets.UpdateBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
booking = newbooking.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary := mobilityaccountsstorage.Account{}
|
|
||||||
|
|
||||||
if booking.Driver != "" {
|
|
||||||
beneficiaryrequest := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: booking.Driver,
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), beneficiaryrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary = beneficiaryresp.Account.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
grouprequest := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: booking.Vehicle.Administrators[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), grouprequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
alternativerequest := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
Types: []string{booking.Vehicle.Type},
|
|
||||||
Administrators: booking.Vehicle.Administrators,
|
|
||||||
AvailabilityFrom: timestamppb.New(booking.Startdate),
|
|
||||||
AvailabilityTo: timestamppb.New(booking.Enddate.Add(24 * time.Hour)),
|
|
||||||
}
|
|
||||||
|
|
||||||
alternativeresp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), alternativerequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
alternatives := []any{}
|
|
||||||
|
|
||||||
for _, a := range alternativeresp.Vehicles {
|
|
||||||
alternatives = append(alternatives, a.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingid)
|
|
||||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
|
||||||
|
|
||||||
/* diag := []diagsstorage.Diag{}
|
|
||||||
|
|
||||||
diagsrequest := &diags.GetDiagsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_bookings"},
|
|
||||||
}
|
|
||||||
|
|
||||||
diagsresp, err := h.services.GRPC.Diags.GetDiags(context.TODO(), diagsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range diagsresp.Diags {
|
|
||||||
diagData := d.Data.AsMap()
|
|
||||||
if booking, ok := diagData["booking"].(string); ok && booking == bookingid {
|
|
||||||
diag = append(diag, d.ToStorageType())
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
diagsAny := []any{}
|
|
||||||
/*for i, d := range diag {
|
|
||||||
diagsAny[i] = d
|
|
||||||
}*/
|
|
||||||
|
|
||||||
h.Renderer.VehicleManagementBookingDisplay(w, r, booking, booking.Vehicle, beneficiary, groupresp.Group.ToStorageType(), documents, file_types_map, alternatives, diagsAny)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) VehicleManagementBookingChangeVehicle(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingid := vars["bookingid"]
|
|
||||||
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
newvehicle := r.FormValue("vehicle")
|
|
||||||
|
|
||||||
booking, err := h.services.GetBooking(bookingid)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
booking.Vehicleid = newvehicle
|
|
||||||
|
|
||||||
b, _ := fleets.BookingFromStorageType(&booking)
|
|
||||||
|
|
||||||
request := &fleets.UpdateBookingRequest{
|
|
||||||
Booking: b,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.Fleets.UpdateBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/bookings/%s", bookingid), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) VehiclesFleetMakeUnavailable(w http.ResponseWriter, r *http.Request) { // Get Group
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
log.Error().Msg("no current group")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current_group := g.(storage.Group)
|
|
||||||
|
|
||||||
// Get current user ID
|
|
||||||
u := r.Context().Value(identification.IdtokenKey)
|
|
||||||
if u == nil {
|
|
||||||
log.Error().Msg("no current user")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current_user_token := u.(*oidc.IDToken)
|
|
||||||
|
|
||||||
// Get current user claims
|
|
||||||
c := r.Context().Value(identification.ClaimsKey)
|
|
||||||
if c == nil {
|
|
||||||
log.Error().Msg("no current user claims")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current_user_claims := c.(map[string]any)
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
vehicleid := vars["vehicleid"]
|
|
||||||
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
start := r.FormValue("unavailablefrom")
|
|
||||||
end := r.FormValue("unavailableto")
|
|
||||||
comment := r.FormValue("comment")
|
|
||||||
|
|
||||||
unavailablefrom, _ := time.Parse("2006-01-02", start)
|
|
||||||
unavailableto, _ := time.Parse("2006-01-02", end)
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"comment": comment,
|
|
||||||
"administrator_unavailability": true,
|
|
||||||
"booked_by": map[string]any{
|
|
||||||
"user": map[string]any{
|
|
||||||
"id": current_user_token.Subject,
|
|
||||||
"display_name": current_user_claims["display_name"],
|
|
||||||
},
|
|
||||||
"group": map[string]any{
|
|
||||||
"id": current_group.ID,
|
|
||||||
"name": current_group.Data["name"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
datapb, err := structpb.NewStruct(data)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
booking := &fleets.Booking{
|
|
||||||
Id: uuid.NewString(),
|
|
||||||
Vehicleid: vehicleid,
|
|
||||||
Unavailablefrom: timestamppb.New(unavailablefrom),
|
|
||||||
Unavailableto: timestamppb.New(unavailableto),
|
|
||||||
Data: datapb,
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &fleets.CreateBookingRequest{
|
|
||||||
Booking: booking,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.Fleets.CreateBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", vehicleid), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) DeleteBooking(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingid := vars["bookingid"]
|
|
||||||
|
|
||||||
request := &fleets.DeleteBookingRequest{
|
|
||||||
Id: bookingid,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.Fleets.DeleteBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/vehicles-management/bookings/", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) UnbookingVehicle(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingid := vars["bookingid"]
|
|
||||||
|
|
||||||
request := &fleets.GetBookingRequest{
|
|
||||||
Bookingid: bookingid,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// now := time.Now()
|
|
||||||
// date := now.Format("2006-01-02")
|
|
||||||
|
|
||||||
date := "1970-01-01"
|
|
||||||
unavailableto, _ := time.Parse("2006-01-02", date)
|
|
||||||
unavailablefrom := unavailableto
|
|
||||||
|
|
||||||
current_group, err := h.currentGroup(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_user_token, current_user_claims, err := h.currentUser(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
booked_by_id := resp.Booking.Data.Fields["booked_by"].GetStructValue().Fields["user"].GetStructValue().Fields["id"].GetStringValue()
|
|
||||||
booked_by_name := resp.Booking.Data.Fields["booked_by"].GetStructValue().Fields["user"].GetStructValue().Fields["display_name"].GetStringValue()
|
|
||||||
booked_by_email := resp.Booking.Data.Fields["booked_by"].GetStructValue().Fields["user"].GetStructValue().Fields["email"].GetStringValue()
|
|
||||||
booked_by_group_id := resp.Booking.Data.Fields["booked_by"].GetStructValue().Fields["group"].GetStructValue().Fields["id"].GetStringValue()
|
|
||||||
booked_by_group_name := resp.Booking.Data.Fields["booked_by"].GetStructValue().Fields["group"].GetStructValue().Fields["name"].GetStringValue()
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"booked_by": map[string]any{
|
|
||||||
"user": map[string]any{
|
|
||||||
"id": booked_by_id,
|
|
||||||
"display_name": booked_by_name,
|
|
||||||
"email": booked_by_email,
|
|
||||||
},
|
|
||||||
"group": map[string]any{
|
|
||||||
"id": booked_by_group_id,
|
|
||||||
"name": booked_by_group_name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"unbooked_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"],
|
|
||||||
},
|
|
||||||
"group": map[string]any{
|
|
||||||
"id": current_group.ID,
|
|
||||||
"name": current_group.Data["name"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Deleted": true,
|
|
||||||
"motif": r.FormValue("motif"),
|
|
||||||
}
|
|
||||||
|
|
||||||
datapb, err := structpb.NewStruct(data)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
|
|
||||||
request := &fleets.UpdateBookingRequest{
|
|
||||||
Booking: &fleets.Booking{
|
|
||||||
Id: resp.Booking.Id,
|
|
||||||
Vehicleid: resp.Booking.Vehicleid,
|
|
||||||
Driver: resp.Booking.Driver,
|
|
||||||
Startdate: resp.Booking.Startdate,
|
|
||||||
Enddate: resp.Booking.Enddate,
|
|
||||||
Unavailablefrom: timestamppb.New(unavailablefrom),
|
|
||||||
Unavailableto: timestamppb.New(unavailableto),
|
|
||||||
Data: datapb,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.Fleets.UpdateBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/vehicles-management/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Renderer.UnbookingVehicle(w, r, resp.Booking.ToStorageType())
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////UpdateVehicle///////////////////////
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
vehicleID := vars["vehicleid"]
|
|
||||||
request := &fleets.GetVehicleRequest{
|
|
||||||
Vehicleid: vehicleID,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetVehicle(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
namespaceV := resp.Vehicle.Namespace
|
|
||||||
// typeV := resp.Vehicle.Type
|
|
||||||
administratorsV := resp.Vehicle.Administrators
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
fmt.Print(r.FormValue("vehicle_type"))
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dataMap := map[string]any{}
|
|
||||||
if v := r.FormValue("name"); v != "" {
|
|
||||||
dataMap["name"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("address"); v != "" {
|
|
||||||
var address map[string]any
|
|
||||||
err := json.Unmarshal([]byte(v), &address)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dataMap["address"] = address
|
|
||||||
}
|
|
||||||
if v := r.FormValue("informations"); v != "" {
|
|
||||||
dataMap["informations"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("licence_plate"); v != "" {
|
|
||||||
dataMap["licence_plate"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("kilometers"); v != "" {
|
|
||||||
dataMap["kilometers"] = v
|
|
||||||
}
|
|
||||||
if v := r.FormValue("automatic"); v != "" {
|
|
||||||
dataMap["automatic"] = (v == "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := structpb.NewValue(dataMap)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &fleets.UpdateVehicleRequest{
|
|
||||||
Vehicle: &fleets.Vehicle{
|
|
||||||
Id: vehicleID,
|
|
||||||
Namespace: namespaceV,
|
|
||||||
Type: r.FormValue("type"),
|
|
||||||
Administrators: administratorsV,
|
|
||||||
Data: data.GetStructValue(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Fleets.UpdateVehicle(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", resp.Vehicle.Id), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vehicles_types := h.config.GetStringSlice("modules.fleets.vehicle_types")
|
|
||||||
h.Renderer.VehiclesFleetUpdate(w, r, resp.Vehicle.ToStorageType(), vehicles_types)
|
|
||||||
}
|
|
||||||
|
|
@ -1,415 +0,0 @@
|
||||||
package application
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
"git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
groupsmanagementstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/paulmach/orb/geojson"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h ApplicationHandler) VehiclesSearch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.ParseForm()
|
|
||||||
var beneficiary mobilityaccountsstorage.Account
|
|
||||||
|
|
||||||
beneficiarydocuments := []filestorage.FileInfo{}
|
|
||||||
|
|
||||||
vehicles := []storage.Vehicle{}
|
|
||||||
searched := false
|
|
||||||
start := r.FormValue("startdate")
|
|
||||||
end := r.FormValue("enddate")
|
|
||||||
|
|
||||||
startdate, _ := time.Parse("2006-01-02", start)
|
|
||||||
enddate, _ := time.Parse("2006-01-02", end)
|
|
||||||
automatic := (r.FormValue("automatic") == "on")
|
|
||||||
|
|
||||||
administrators := []string{}
|
|
||||||
|
|
||||||
if r.FormValue("beneficiaryid") != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) {
|
|
||||||
// Handler form
|
|
||||||
searched = true
|
|
||||||
|
|
||||||
requestbeneficiary := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: r.FormValue("beneficiaryid"),
|
|
||||||
}
|
|
||||||
|
|
||||||
respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary = respbeneficiary.Account.ToStorageType()
|
|
||||||
|
|
||||||
request := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
AvailabilityFrom: timestamppb.New(startdate),
|
|
||||||
AvailabilityTo: timestamppb.New(enddate),
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.FormValue("type") != "" {
|
|
||||||
request.Types = []string{r.FormValue("type")}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vehicle := range resp.Vehicles {
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
|
|
||||||
if r.FormValue("type") == "Voiture" && automatic {
|
|
||||||
if auto, ok := v.Data["automatic"].(bool); !ok || !auto {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
adminfound := false
|
|
||||||
for _, a := range administrators {
|
|
||||||
if a == v.Administrators[0] {
|
|
||||||
adminfound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !adminfound {
|
|
||||||
administrators = append(administrators, v.Administrators[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles = append(vehicles, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort vehicles if beneficiary address is set
|
|
||||||
if beneficiaryAddress, ok := beneficiary.Data["address"]; ok {
|
|
||||||
beneficiaryAddressJson, err := json.Marshal(beneficiaryAddress)
|
|
||||||
if err == nil {
|
|
||||||
beneficiaryAddressGeojson, err := geojson.UnmarshalFeature(beneficiaryAddressJson)
|
|
||||||
if err == nil {
|
|
||||||
slices.SortFunc(vehicles, sorting.VehiclesByDistanceFrom(*beneficiaryAddressGeojson))
|
|
||||||
} else {
|
|
||||||
log.Error().Err(err).Msg("error transforming beneficiary address to GeoJSON")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Error().Err(err).Msg("error transforming beneficiary address to JSON")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiarydocuments = h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiary.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts, err := h.beneficiaries(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
groups := map[string]any{}
|
|
||||||
|
|
||||||
if len(administrators) > 0 {
|
|
||||||
admingroups, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: administrators,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range admingroups.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
sort.Sort(sorting.BeneficiariesByName(accounts))
|
|
||||||
|
|
||||||
mandatory_documents := h.config.GetStringSlice("modules.fleets.booking_documents.mandatory")
|
|
||||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
|
||||||
vehicles_types := h.config.GetStringSlice("modules.fleets.vehicle_types")
|
|
||||||
|
|
||||||
h.Renderer.VehiclesSearch(w, r, accounts, searched, vehicles, beneficiary, r.FormValue("startdate"), r.FormValue("enddate"), mandatory_documents, file_types_map, beneficiarydocuments, r.FormValue("type"), automatic, vehicles_types, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) Book(w http.ResponseWriter, r *http.Request) {
|
|
||||||
current_group, err := h.currentGroup(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_user_token, current_user_claims, err := h.currentUser(r)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
vehicleid := vars["vehicleid"]
|
|
||||||
beneficiaryid := vars["beneficiaryid"]
|
|
||||||
|
|
||||||
vehicle, err := h.services.GRPC.Fleets.GetVehicle(context.TODO(), &fleets.GetVehicleRequest{
|
|
||||||
Vehicleid: vehicleid,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte("Vehicle not found"))
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ParseMultipartForm(100 * 1024 * 1024)
|
|
||||||
|
|
||||||
start := r.FormValue("startdate")
|
|
||||||
end := r.FormValue("enddate")
|
|
||||||
|
|
||||||
startdate, _ := time.Parse("2006-01-02", start)
|
|
||||||
enddate, _ := time.Parse("2006-01-02", end)
|
|
||||||
|
|
||||||
data := map[string]any{
|
|
||||||
"booked_by": map[string]any{
|
|
||||||
"user": map[string]any{
|
|
||||||
"id": current_user_token.Subject,
|
|
||||||
"display_name": fmt.Sprintf("%s %s", current_user_claims["first_name"], current_user_claims["last_name"]),
|
|
||||||
},
|
|
||||||
"group": map[string]any{
|
|
||||||
"id": current_group.ID,
|
|
||||||
"name": current_group.Data["name"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
datapb, err := structpb.NewStruct(data)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
booking := &fleets.Booking{
|
|
||||||
Id: uuid.NewString(),
|
|
||||||
Vehicleid: vehicleid,
|
|
||||||
Driver: beneficiaryid,
|
|
||||||
Startdate: timestamppb.New(startdate),
|
|
||||||
Enddate: timestamppb.New(enddate),
|
|
||||||
Unavailablefrom: timestamppb.New(startdate),
|
|
||||||
Unavailableto: timestamppb.New(enddate.Add(72 * time.Hour)),
|
|
||||||
Data: datapb,
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &fleets.CreateBookingRequest{
|
|
||||||
Booking: booking,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range h.config.GetStringSlice("modules.fleets.booking_documents.mandatory") {
|
|
||||||
existing_file := r.FormValue("type-" + v)
|
|
||||||
if existing_file == "" {
|
|
||||||
file, header, err := r.FormFile("doc-" + v)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte("Document manquant : " + v))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
fileid := uuid.NewString()
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
|
||||||
"type": v,
|
|
||||||
"name": header.Filename,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.filestorage.Put(file, filestorage.PREFIX_BOOKINGS, fmt.Sprintf("%s/%s_%s", booking.Id, fileid, header.Filename), header.Size, metadata); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path := strings.Split(existing_file, "/")
|
|
||||||
|
|
||||||
if err := h.filestorage.Copy(existing_file, fmt.Sprintf("%s/%s/%s", filestorage.PREFIX_BOOKINGS, booking.Id, path[len(path)-1])); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.Fleets.CreateBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTIFY GROUP MEMBERS
|
|
||||||
members, _, err := h.groupmembers(vehicle.Vehicle.Administrators[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
} else {
|
|
||||||
for _, m := range members {
|
|
||||||
if email, ok := m.Data["email"].(string); ok {
|
|
||||||
h.emailing.Send("fleets.bookings.creation_admin_alert", email, map[string]string{
|
|
||||||
"bookingid": booking.Id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles/bookings/%s", booking.Id), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) VehicleBookingDisplay(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingid := vars["bookingid"]
|
|
||||||
|
|
||||||
request := &fleets.GetBookingRequest{
|
|
||||||
Bookingid: bookingid,
|
|
||||||
}
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetBooking(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
booking := resp.Booking.ToStorageType()
|
|
||||||
|
|
||||||
beneficiaryrequest := &mobilityaccounts.GetAccountRequest{
|
|
||||||
Id: booking.Driver,
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), beneficiaryrequest)
|
|
||||||
if err != nil {
|
|
||||||
beneficiaryresp = &mobilityaccounts.GetAccountResponse{
|
|
||||||
Account: &mobilityaccounts.Account{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
grouprequest := &groupsmanagement.GetGroupRequest{
|
|
||||||
Id: booking.Vehicle.Administrators[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), grouprequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
documents := h.filestorage.List(filestorage.PREFIX_BOOKINGS + "/" + bookingid)
|
|
||||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
|
||||||
|
|
||||||
/*diag := []diagsstorage.Diag{}
|
|
||||||
|
|
||||||
diagsrequest := &diags.GetDiagsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_bookings"},
|
|
||||||
}
|
|
||||||
|
|
||||||
diagsresp, err := h.services.GRPC.Diags.GetDiags(context.TODO(), diagsrequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range diagsresp.Diags {
|
|
||||||
diagData := d.Data.AsMap()
|
|
||||||
if booking, ok := diagData["booking"].(string); ok && booking == bookingid {
|
|
||||||
diag = append(diag, d.ToStorageType())
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
diagsAny := []any{}
|
|
||||||
/*for i, d := range diag {
|
|
||||||
diagsAny[i] = d
|
|
||||||
}*/
|
|
||||||
|
|
||||||
h.Renderer.VehicleBookingDisplay(w, r, booking, booking.Vehicle, beneficiaryresp.Account.ToStorageType(), groupresp.Group.ToStorageType(), documents, file_types_map, diagsAny)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h ApplicationHandler) VehiclesBookingsList(w http.ResponseWriter, r *http.Request) {
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
if g == nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group := g.(groupsmanagementstorage.Group)
|
|
||||||
|
|
||||||
request := &fleets.GetBookingsRequest{}
|
|
||||||
resp, err := h.services.GRPC.Fleets.GetBookings(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bookings := []storage.Booking{}
|
|
||||||
|
|
||||||
for _, b := range resp.Bookings {
|
|
||||||
booking := b.ToStorageType()
|
|
||||||
if b1, ok := booking.Data["booked_by"].(map[string]any); ok {
|
|
||||||
if b2, ok := b1["group"].(map[string]any); ok {
|
|
||||||
if b2["id"] == group.ID {
|
|
||||||
bookings = append(bookings, booking)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
|
||||||
|
|
||||||
vehicles, _ := h.services.GetVehiclesMap()
|
|
||||||
groups, _ := h.services.GetGroupsMap()
|
|
||||||
|
|
||||||
h.Renderer.VehicleBookingsList(w, r, bookings, vehicles, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ApplicationHandler) BookingDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bookingid := vars["bookingid"]
|
|
||||||
document := vars["document"]
|
|
||||||
|
|
||||||
log.Debug().Str("booking id", bookingid).Str("document", document).Msg("Document and booking ID")
|
|
||||||
|
|
||||||
file, info, err := h.filestorage.Get(filestorage.PREFIX_BOOKINGS, fmt.Sprintf("%s/%s", bookingid, document))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", info.ContentType)
|
|
||||||
if _, err = io.Copy(w, file); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/app/vehicles/bookings/%s", bookingid), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
"git.coopgo.io/coopgo-platform/emailing"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AuthHandler struct {
|
|
||||||
idp *identification.IdentificationProvider
|
|
||||||
config *viper.Viper
|
|
||||||
services *services.ServicesHandler
|
|
||||||
Renderer *renderer.Renderer
|
|
||||||
cache cache.CacheHandler
|
|
||||||
emailing *emailing.Mailer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler, emailing *emailing.Mailer, filestorage cache.FileStorage) (*AuthHandler, error) {
|
|
||||||
templates_root := cfg.GetString("templates.root")
|
|
||||||
renderer := renderer.NewRenderer(cfg, templates_root, filestorage)
|
|
||||||
return &AuthHandler{
|
|
||||||
idp: idp,
|
|
||||||
config: cfg,
|
|
||||||
services: svc,
|
|
||||||
Renderer: renderer,
|
|
||||||
cache: cache,
|
|
||||||
emailing: emailing,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *AuthHandler) Groups(w http.ResponseWriter, r *http.Request) {
|
|
||||||
session, _ := h.idp.SessionsStore.Get(r, "parcoursmob_session")
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
groupid := r.FormValue("group")
|
|
||||||
|
|
||||||
session.Values["organization"] = groupid
|
|
||||||
session.Save(r, w)
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenstring, ok := session.Values["idtoken"]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
idtoken, err := h.idp.TokenVerifier.Verify(context.Background(), tokenstring.(string))
|
|
||||||
if err != nil {
|
|
||||||
delete(session.Values, "idtoken")
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var claims map[string]any
|
|
||||||
|
|
||||||
err = idtoken.Claims(&claims)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
g := claims["groups"]
|
|
||||||
|
|
||||||
groups_interface, ok := g.([]any)
|
|
||||||
if !ok {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := []string{}
|
|
||||||
|
|
||||||
for _, v := range groups_interface {
|
|
||||||
groups = append(groups, v.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groups,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), request)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupsresponse = []any{}
|
|
||||||
|
|
||||||
for _, group := range resp.Groups {
|
|
||||||
if group.Namespace != "parcoursmob_organizations" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
g := group.ToStorageType()
|
|
||||||
groupsresponse = append(groupsresponse, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.AuthGroups(w, r, groupsresponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *AuthHandler) GroupSwitch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
session, _ := h.idp.SessionsStore.Get(r, "parcoursmob_session")
|
|
||||||
delete(session.Values, "organization")
|
|
||||||
session.Save(r, w)
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *AuthHandler) LostPasswordInit(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
r.ParseForm()
|
|
||||||
email := r.FormValue("email")
|
|
||||||
if email != "" {
|
|
||||||
account, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &grpcapi.GetAccountUsernameRequest{
|
|
||||||
Username: email,
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, 16)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key := base64.RawURLEncoding.EncodeToString(b)
|
|
||||||
|
|
||||||
passwordretrieval := map[string]any{
|
|
||||||
"username": email,
|
|
||||||
"account_id": account.Account.Id,
|
|
||||||
"key": key,
|
|
||||||
}
|
|
||||||
|
|
||||||
h.cache.PutWithTTL("retrieve-password/"+key, passwordretrieval, 72*time.Hour)
|
|
||||||
|
|
||||||
if err := h.emailing.Send("auth.retrieve_password", email, passwordretrieval); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h.Renderer.LostPasswordInit(w, r)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *AuthHandler) LostPasswordRecover(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
key := r.FormValue("key")
|
|
||||||
recover, err := h.cache.Get("retrieve-password/" + key)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
h.Renderer.LostPasswordRecoverKO(w, r, key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
newpassword := r.FormValue("password")
|
|
||||||
if newpassword == "" {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte("Password is empty"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := h.services.GRPC.MobilityAccounts.ChangePassword(context.TODO(), &grpcapi.ChangePasswordRequest{
|
|
||||||
Id: recover.(map[string]any)["account_id"].(string),
|
|
||||||
Password: newpassword,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.cache.Delete("retrieve-password/" + key)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
|
|
||||||
}
|
|
||||||
h.Renderer.LostPasswordRecover(w, r, recover)
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
ma "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *AuthHandler) Onboarding(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.ParseForm()
|
|
||||||
|
|
||||||
key := r.FormValue("key")
|
|
||||||
onboarding, err := h.cache.Get("onboarding/" + key)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
h.Renderer.AuthOnboardingKO(w, r, key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onboardingmap := onboarding.(map[string]any)
|
|
||||||
|
|
||||||
if r.Method == "POST" {
|
|
||||||
if r.FormValue("password") == "" {
|
|
||||||
log.Error().Msg("Password is empty")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := []string{
|
|
||||||
onboardingmap["group"].(string),
|
|
||||||
//onboardingmap["group"].(string) + ":admin",
|
|
||||||
}
|
|
||||||
|
|
||||||
if onboardingmap["admin"].(bool) {
|
|
||||||
groups = append(groups, onboardingmap["group"].(string)+":admin")
|
|
||||||
}
|
|
||||||
display_name := fmt.Sprint(r.FormValue("first_name")) + " " + fmt.Sprint(r.FormValue("last_name"))
|
|
||||||
account := &ma.Account{
|
|
||||||
Authentication: ma.AccountAuth{
|
|
||||||
Local: ma.LocalAuth{
|
|
||||||
Username: onboardingmap["username"].(string),
|
|
||||||
Password: r.FormValue("password"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Namespace: "parcoursmob",
|
|
||||||
|
|
||||||
Data: map[string]any{
|
|
||||||
"display_name": display_name,
|
|
||||||
"first_name": r.FormValue("first_name"),
|
|
||||||
"last_name": r.FormValue("last_name"),
|
|
||||||
"email": onboardingmap["username"],
|
|
||||||
"groups": groups,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
acc, err := mobilityaccounts.AccountFromStorageType(account)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &mobilityaccounts.RegisterRequest{
|
|
||||||
Account: acc,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.services.GRPC.MobilityAccounts.Register(context.TODO(), request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.cache.Delete("onboarding/" + key)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Renderer.AuthOnboarding(w, r, key, onboarding)
|
|
||||||
}
|
|
||||||
|
|
@ -1,205 +0,0 @@
|
||||||
package exports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
|
||||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
|
||||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
accountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/xuri/excelize/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ExportsHandler) Agenda(filter string) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch filter {
|
|
||||||
case "allEvents":
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvents(context.TODO(), &agenda.GetEventsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_dispositifs"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
events := []agendastorage.Event{}
|
|
||||||
|
|
||||||
groupids := []string{}
|
|
||||||
beneficiaries_ids := []string{}
|
|
||||||
for _, e := range resp.Events {
|
|
||||||
groupids = append(groupids, e.Owners...)
|
|
||||||
events = append(events, e.ToStorageType())
|
|
||||||
|
|
||||||
for _, subscriptions := range e.Subscriptions {
|
|
||||||
beneficiaries_ids = append(beneficiaries_ids, subscriptions.Subscriber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(sorting.EventsByStartdate(events))
|
|
||||||
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groups := map[string]groupsstorage.Group{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, g := range groupsresp.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: beneficiaries_ids,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_map := map[string]accountsstorage.Account{}
|
|
||||||
for _, ben := range beneficiaries.Accounts {
|
|
||||||
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
f := h.generateExcel(events, groups, beneficiaries_map)
|
|
||||||
|
|
||||||
h.writeFileResponse(f, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "oneEvent":
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
eventId := vars["eventid"]
|
|
||||||
resp, err := h.services.GRPC.Agenda.GetEvent(context.TODO(), &agenda.GetEventRequest{
|
|
||||||
Id: eventId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
groupids := []string{}
|
|
||||||
beneficiaries_ids := []string{}
|
|
||||||
groupids = append(groupids, resp.Event.Owners...)
|
|
||||||
for _, subscriptions := range resp.Event.Subscriptions {
|
|
||||||
beneficiaries_ids = append(beneficiaries_ids, subscriptions.Subscriber)
|
|
||||||
}
|
|
||||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
|
||||||
Groupids: groupids,
|
|
||||||
})
|
|
||||||
groups := map[string]groupsstorage.Group{}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, g := range groupsresp.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: beneficiaries_ids,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_map := map[string]accountsstorage.Account{}
|
|
||||||
for _, ben := range beneficiaries.Accounts {
|
|
||||||
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
f := h.generateExcel([]agendastorage.Event{resp.Event.ToStorageType()}, groups, beneficiaries_map)
|
|
||||||
h.writeFileResponse(f, w)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ExportsHandler) generateExcel(events []agendastorage.Event, groups map[string]groupsstorage.Group,
|
|
||||||
beneficiaries_map map[string]accountsstorage.Account) *excelize.File {
|
|
||||||
f := excelize.NewFile()
|
|
||||||
defer func() {
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
f.SetCellValue("Sheet1", "A1", "Evénement")
|
|
||||||
f.SetCellValue("Sheet1", "B1", "Date de début")
|
|
||||||
f.SetCellValue("Sheet1", "C1", "Date de fin")
|
|
||||||
f.SetCellValue("Sheet1", "D1", "Nom bénéficiaire")
|
|
||||||
f.SetCellValue("Sheet1", "E1", "Prenom bénéficiaire")
|
|
||||||
f.SetCellValue("Sheet1", "F1", "Numéro allocataire / Pole emploi")
|
|
||||||
f.SetCellValue("Sheet1", "G1", "Prescipteur")
|
|
||||||
f.SetCellValue("Sheet1", "H1", "Prescipteur Nom")
|
|
||||||
f.SetCellValue("Sheet1", "I1", "Prescipteur Email")
|
|
||||||
f.SetCellValue("Sheet1", "J1", "Gestionnaire événement")
|
|
||||||
// f.SetCellValue("Sheet1", "I1", "Prescripteur téléphone")
|
|
||||||
|
|
||||||
i := 2
|
|
||||||
for _, e := range events {
|
|
||||||
if len(e.Owners) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
admin := groups[e.Owners[0]]
|
|
||||||
|
|
||||||
for _, s := range e.Subscriptions {
|
|
||||||
subscribedbygroup := ""
|
|
||||||
subscribedbyuser := ""
|
|
||||||
subscribedbyemail := ""
|
|
||||||
if v, ok := s.Data["subscribed_by"].(map[string]any); ok {
|
|
||||||
if v2, ok := v["group"].(map[string]any); ok {
|
|
||||||
if v3, ok := v2["name"].(string); ok {
|
|
||||||
subscribedbygroup = v3
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if v4, ok := v["user"].(map[string]any); ok {
|
|
||||||
if v5, ok := v4["display_name"].(string); ok {
|
|
||||||
subscribedbyuser = v5
|
|
||||||
}
|
|
||||||
if v6, ok := v4["email"].(string); ok {
|
|
||||||
subscribedbyemail = v6
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary := beneficiaries_map[s.Subscriber]
|
|
||||||
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i), e.Name)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i), e.Startdate.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i), e.Enddate.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i), beneficiary.Data["last_name"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i), beneficiary.Data["first_name"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i), beneficiary.Data["file_number"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i), subscribedbygroup)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i), subscribedbyuser)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("I%d", i), subscribedbyemail)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("J%d", i), admin.Data["name"])
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ExportsHandler) writeFileResponse(file *excelize.File, w http.ResponseWriter) {
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+"Workbook.xlsx")
|
|
||||||
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
||||||
w.Header().Set("Expires", "0")
|
|
||||||
file.Write(w)
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package exports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
|
||||||
"git.coopgo.io/coopgo-platform/emailing"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExportsHandler struct {
|
|
||||||
config *viper.Viper
|
|
||||||
services *services.ServicesHandler
|
|
||||||
emailing *emailing.Mailer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewExportsHandler(cfg *viper.Viper, svc *services.ServicesHandler, emailing *emailing.Mailer) (*ExportsHandler, error) {
|
|
||||||
return &ExportsHandler{
|
|
||||||
config: cfg,
|
|
||||||
services: svc,
|
|
||||||
emailing: emailing,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,308 +0,0 @@
|
||||||
package exports
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
|
||||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
|
||||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
|
||||||
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
|
||||||
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
|
||||||
accountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/xuri/excelize/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *ExportsHandler) Bookings(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vehicles := map[string]fleetsstorage.Vehicle{}
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
reequest := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
reesp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), reequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_ids := []string{}
|
|
||||||
|
|
||||||
for _, vehicle := range reesp.Vehicles {
|
|
||||||
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
|
|
||||||
for _, b := range v.Bookings {
|
|
||||||
bookings = append(bookings, b)
|
|
||||||
beneficiaries_ids = append(beneficiaries_ids, b.Driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles[vehicle.Id] = v
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := map[string]groupsstorage.Group{}
|
|
||||||
|
|
||||||
admingroups, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_organizations"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range admingroups.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: beneficiaries_ids,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_map := map[string]accountsstorage.Account{}
|
|
||||||
for _, ben := range beneficiaries.Accounts {
|
|
||||||
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////// Generate file
|
|
||||||
|
|
||||||
f := excelize.NewFile()
|
|
||||||
defer func() {
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
f.SetCellValue("Sheet1", "A1", "Numéro")
|
|
||||||
f.SetCellValue("Sheet1", "B1", "Type")
|
|
||||||
f.SetCellValue("Sheet1", "C1", "Gestionnaire")
|
|
||||||
f.SetCellValue("Sheet1", "D1", "Prescripteur")
|
|
||||||
f.SetCellValue("Sheet1", "E1", "Bénéficiaire")
|
|
||||||
f.SetCellValue("Sheet1", "F1", "Numéro allocataire / Pole emploi")
|
|
||||||
f.SetCellValue("Sheet1", "G1", "Début de Mise à disposition")
|
|
||||||
f.SetCellValue("Sheet1", "H1", "Fin de mise à disposition")
|
|
||||||
f.SetCellValue("Sheet1", "I1", "Début indisponibilité")
|
|
||||||
f.SetCellValue("Sheet1", "J1", "Fin indisponibilité")
|
|
||||||
f.SetCellValue("Sheet1", "K1", "Véhicule retiré")
|
|
||||||
f.SetCellValue("Sheet1", "L1", "Commentaire - Retrait véhicule")
|
|
||||||
f.SetCellValue("Sheet1", "M1", "Réservation supprimée")
|
|
||||||
f.SetCellValue("Sheet1", "N1", "Motif de la suppression")
|
|
||||||
|
|
||||||
i := 2
|
|
||||||
for _, b := range bookings {
|
|
||||||
vehicle := vehicles[b.Vehicleid]
|
|
||||||
if len(vehicle.Administrators) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
admin := groups[vehicle.Administrators[0]]
|
|
||||||
|
|
||||||
bookedby := ""
|
|
||||||
if v, ok := b.Data["booked_by"].(map[string]any); ok {
|
|
||||||
if v2, ok := v["user"].(map[string]any); ok {
|
|
||||||
if v3, ok := v2["display_name"].(string); ok {
|
|
||||||
bookedby = v3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bookedbygroup := ""
|
|
||||||
if v4, ok := b.Data["booked_by"].(map[string]any); ok {
|
|
||||||
if v5, ok := v4["group"].(map[string]any); ok {
|
|
||||||
if v6, ok := v5["id"].(string); ok {
|
|
||||||
bookedbygroup = v6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter by group
|
|
||||||
g := r.Context().Value(identification.GroupKey)
|
|
||||||
group := g.(groupsstorage.Group)
|
|
||||||
|
|
||||||
if bookedbygroup != group.ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary := beneficiaries_map[b.Driver]
|
|
||||||
adminunavailability := false
|
|
||||||
|
|
||||||
if av, ok := b.Data["administrator_unavailability"].(bool); ok && av {
|
|
||||||
adminunavailability = true
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted := ""
|
|
||||||
v, ok := b.Data["Deleted"]
|
|
||||||
fmt.Println(v)
|
|
||||||
fmt.Println(ok)
|
|
||||||
if b.Deleted || (ok && v.(bool)) {
|
|
||||||
deleted = "DELETED"
|
|
||||||
}
|
|
||||||
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i), vehicle.Data["licence_plate"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i), vehicle.Type)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i), admin.Data["name"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i), bookedby)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i), fmt.Sprintf("%v %v", beneficiary.Data["first_name"], beneficiary.Data["last_name"]))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i), beneficiary.Data["file_number"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i), b.Startdate.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i), b.Enddate.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("I%d", i), b.Unavailablefrom.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("J%d", i), b.Unavailableto.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("K%d", i), adminunavailability)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("L%d", i), b.Data["comment"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("M%d", i), deleted)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("N%d", i), b.Data["motif"])
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+"Workbook.xlsx")
|
|
||||||
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
||||||
w.Header().Set("Expires", "0")
|
|
||||||
f.Write(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ExportsHandler) AllBookings(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vehicles := map[string]fleetsstorage.Vehicle{}
|
|
||||||
bookings := []fleetsstorage.Booking{}
|
|
||||||
reequest := &fleets.GetVehiclesRequest{
|
|
||||||
Namespaces: []string{"parcoursmob"},
|
|
||||||
}
|
|
||||||
reesp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), reequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_ids := []string{}
|
|
||||||
|
|
||||||
for _, vehicle := range reesp.Vehicles {
|
|
||||||
|
|
||||||
v := vehicle.ToStorageType()
|
|
||||||
|
|
||||||
for _, b := range v.Bookings {
|
|
||||||
bookings = append(bookings, b)
|
|
||||||
beneficiaries_ids = append(beneficiaries_ids, b.Driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
vehicles[vehicle.Id] = v
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := map[string]groupsstorage.Group{}
|
|
||||||
|
|
||||||
admingroups, err := h.services.GRPC.GroupsManagement.GetGroups(context.TODO(), &groupsmanagement.GetGroupsRequest{
|
|
||||||
Namespaces: []string{"parcoursmob_organizations"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range admingroups.Groups {
|
|
||||||
groups[g.Id] = g.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), &accounts.GetAccountsBatchRequest{
|
|
||||||
Accountids: beneficiaries_ids,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiaries_map := map[string]accountsstorage.Account{}
|
|
||||||
for _, ben := range beneficiaries.Accounts {
|
|
||||||
beneficiaries_map[ben.Id] = ben.ToStorageType()
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////// Generate file
|
|
||||||
|
|
||||||
f := excelize.NewFile()
|
|
||||||
defer func() {
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
f.SetCellValue("Sheet1", "A1", "Numéro")
|
|
||||||
f.SetCellValue("Sheet1", "B1", "Type")
|
|
||||||
f.SetCellValue("Sheet1", "C1", "Gestionnaire")
|
|
||||||
f.SetCellValue("Sheet1", "D1", "Prescripteur")
|
|
||||||
f.SetCellValue("Sheet1", "E1", "Bénéficiaire")
|
|
||||||
f.SetCellValue("Sheet1", "F1", "Numéro allocataire / Pole emploi")
|
|
||||||
f.SetCellValue("Sheet1", "G1", "Début de Mise à disposition")
|
|
||||||
f.SetCellValue("Sheet1", "H1", "Fin de mise à disposition")
|
|
||||||
f.SetCellValue("Sheet1", "I1", "Début indisponibilité")
|
|
||||||
f.SetCellValue("Sheet1", "J1", "Fin indisponibilité")
|
|
||||||
f.SetCellValue("Sheet1", "K1", "Véhicule retiré")
|
|
||||||
f.SetCellValue("Sheet1", "L1", "Commentaire - Retrait véhicule")
|
|
||||||
f.SetCellValue("Sheet1", "M1", "Réservation supprimée")
|
|
||||||
f.SetCellValue("Sheet1", "N1", "Motif de la suppression")
|
|
||||||
|
|
||||||
i := 2
|
|
||||||
for _, b := range bookings {
|
|
||||||
vehicle := vehicles[b.Vehicleid]
|
|
||||||
if len(vehicle.Administrators) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
admin := groups[vehicle.Administrators[0]]
|
|
||||||
|
|
||||||
bookedby := ""
|
|
||||||
if v, ok := b.Data["booked_by"].(map[string]any); ok {
|
|
||||||
if v2, ok := v["user"].(map[string]any); ok {
|
|
||||||
if v3, ok := v2["display_name"].(string); ok {
|
|
||||||
bookedby = v3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beneficiary := beneficiaries_map[b.Driver]
|
|
||||||
adminunavailability := false
|
|
||||||
|
|
||||||
if av, ok := b.Data["administrator_unavailability"].(bool); ok && av {
|
|
||||||
adminunavailability = true
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted := ""
|
|
||||||
v, ok := b.Data["Deleted"]
|
|
||||||
fmt.Println(v)
|
|
||||||
fmt.Println(ok)
|
|
||||||
if b.Deleted || (ok && v.(bool)) {
|
|
||||||
deleted = "DELETED"
|
|
||||||
}
|
|
||||||
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i), vehicle.Data["licence_plate"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i), vehicle.Type)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i), admin.Data["name"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i), bookedby)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i), fmt.Sprintf("%v %v", beneficiary.Data["first_name"], beneficiary.Data["last_name"]))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i), beneficiary.Data["file_number"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i), b.Startdate.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i), b.Enddate.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("I%d", i), b.Unavailablefrom.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("J%d", i), b.Unavailableto.Format("2006-01-02"))
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("K%d", i), adminunavailability)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("L%d", i), b.Data["comment"])
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("M%d", i), deleted)
|
|
||||||
f.SetCellValue("Sheet1", fmt.Sprintf("N%d", i), b.Data["motif"])
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename="+"Workbook.xlsx")
|
|
||||||
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
||||||
w.Header().Set("Expires", "0")
|
|
||||||
f.Write(w)
|
|
||||||
}
|
|
||||||
247
main.go
247
main.go
|
|
@ -1,25 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"sync"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/handlers/api"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/handlers/api/protected"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/handlers/application"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/handlers/auth"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/handlers/exports"
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/servers/web"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/protectapi"
|
cache "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -30,10 +23,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
address = cfg.GetString("server.listen")
|
|
||||||
service_name = cfg.GetString("service_name")
|
|
||||||
templates_public_dir = cfg.GetString("templates.public_dir")
|
|
||||||
dev_env = cfg.GetBool("dev_env")
|
dev_env = cfg.GetBool("dev_env")
|
||||||
|
webEnabled = cfg.GetBool("server.web.enabled")
|
||||||
)
|
)
|
||||||
|
|
||||||
if dev_env {
|
if dev_env {
|
||||||
|
|
@ -63,223 +54,21 @@ func main() {
|
||||||
log.Panic().Err(err).Msg("Error creating emailing handler")
|
log.Panic().Err(err).Msg("Error creating emailing handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiHandler, _ := api.NewAPIHandler(cfg, idp, svc, kv)
|
// Create renderer for web server
|
||||||
protectedApiHandler, _ := protected.NewProtectedAPIHandler(cfg, idp, svc, kv)
|
templates_root := cfg.GetString("templates.root")
|
||||||
applicationHandler, _ := application.NewApplicationHandler(cfg, svc, kv, filestorage, emailing)
|
webRenderer := renderer.NewRenderer(cfg, templates_root, filestorage)
|
||||||
exportsHandler, _ := exports.NewExportsHandler(cfg, svc, emailing)
|
|
||||||
authHandler, _ := auth.NewAuthHandler(cfg, idp, svc, kv, emailing, filestorage)
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
applicationHandler, _ := application.NewApplicationHandler(cfg, svc, kv, filestorage, emailing, idp)
|
||||||
|
|
||||||
r.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir(templates_public_dir))))
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
r.HandleFunc("/auth/onboarding", authHandler.Onboarding)
|
if webEnabled {
|
||||||
r.HandleFunc("/auth/disconnect", authHandler.Disconnect)
|
wg.Add(1)
|
||||||
r.HandleFunc("/auth/lost-password", authHandler.LostPasswordInit)
|
go func() {
|
||||||
r.HandleFunc("/auth/lost-password/recover", authHandler.LostPasswordRecover)
|
defer wg.Done()
|
||||||
r.HandleFunc("/auth/groups/", authHandler.Groups)
|
web.Run(cfg, svc, webRenderer, applicationHandler, idp, kv, filestorage)
|
||||||
r.HandleFunc("/auth/groups/switch", authHandler.GroupSwitch)
|
}()
|
||||||
r.HandleFunc("/", redirectApp)
|
|
||||||
|
|
||||||
if dev_env {
|
|
||||||
r.Use(trackPage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calendars_router := r.PathPrefix("/api/calendars").Subrouter()
|
wg.Wait()
|
||||||
calendars_router.HandleFunc("/global.ics", apiHandler.CalendarGlobal)
|
|
||||||
calendars_router.HandleFunc("/organizations/{groupid}.ics", apiHandler.CalendarOrganizations)
|
|
||||||
|
|
||||||
ext_router := r.PathPrefix("/ext").Subrouter()
|
|
||||||
ext_router.HandleFunc("/st/bp/{bookingid}", applicationHandler.SolidarityTransportExternalBookingProposal)
|
|
||||||
|
|
||||||
api_router := r.PathPrefix("/api").Subrouter()
|
|
||||||
api_router.HandleFunc("/", apiHandler.NotFound)
|
|
||||||
api_router.HandleFunc("/geo/autocomplete", apiHandler.GeoAutocomplete)
|
|
||||||
api_router.HandleFunc("/cache/{cacheid}", apiHandler.GetCache)
|
|
||||||
api_router.HandleFunc("/cache/{cacheid}/export", apiHandler.CacheExport)
|
|
||||||
api_router.HandleFunc("/oauth2/callback", apiHandler.OAuth2Callback)
|
|
||||||
|
|
||||||
protected_api_router := api_router.PathPrefix("/protected").Subrouter()
|
|
||||||
protected_api_router.HandleFunc("/users", protectedApiHandler.Users)
|
|
||||||
protected_api_router.Use(protectapi.ApiKey(cfg.GetString("services.api.api_key")))
|
|
||||||
|
|
||||||
interact_router := r.PathPrefix("/int").Subrouter()
|
|
||||||
interact_router.HandleFunc("/", apiHandler.NotFound)
|
|
||||||
|
|
||||||
application := r.PathPrefix("/app").Subrouter()
|
|
||||||
application.HandleFunc("/", applicationHandler.Dashboard)
|
|
||||||
application.HandleFunc("/beneficiaries/", applicationHandler.BeneficiariesList)
|
|
||||||
application.HandleFunc("/beneficiaries/create", applicationHandler.BeneficiaryCreate)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}", applicationHandler.BeneficiaryDisplay)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/update", applicationHandler.BeneficiaryUpdate)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/archive", applicationHandler.BeneficiaryArchive)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/unarchive", applicationHandler.BeneficiaryUnarchive)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/documents", applicationHandler.BeneficiaryDocuments)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/documents/{document}", applicationHandler.BeneficiaryDocumentDownload)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/picture", applicationHandler.BeneficiaryPicture)
|
|
||||||
application.HandleFunc("/wallets/{userid}/credit", applicationHandler.CreditWallet)
|
|
||||||
application.HandleFunc("/members/{beneficiaryid}/picture", applicationHandler.BeneficiaryPicture)
|
|
||||||
application.HandleFunc("/members/{adminid}", applicationHandler.MemberDisplay)
|
|
||||||
application.HandleFunc("/members/{adminid}/update", applicationHandler.MemberUpdate)
|
|
||||||
application.HandleFunc("/members/", applicationHandler.MembersList)
|
|
||||||
application.HandleFunc("/journeys/", applicationHandler.JourneysSearch)
|
|
||||||
application.HandleFunc("/solidarity-transport/", applicationHandler.SolidarityTransportOverview)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/create", applicationHandler.SolidarityTransportCreateDriver)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/update", applicationHandler.SolidarityTransportUpdateDriver)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/availabilities", applicationHandler.SolidarityTransportAddAvailability)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/archive", applicationHandler.SolidarityTransportArchiveDriver)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/unarchive", applicationHandler.SolidarityTransportUnarchiveDriver)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/documents", applicationHandler.SolidarityTransportDriverDocuments)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/documents/{document}", applicationHandler.SolidarityTransportDocumentDownload)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/availabilities/{availabilityid}/delete", applicationHandler.SolidarityTransportDeleteAvailability)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/journeys/{journeyid}", applicationHandler.SolidarityTransportDriverJourney)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}/journeys/{journeyid}/noreturn", applicationHandler.SolidarityTransportDriverJourneyToggleNoreturn)
|
|
||||||
application.HandleFunc("/solidarity-transport/drivers/{driverid}", applicationHandler.SolidarityTransportDriverDisplay)
|
|
||||||
application.HandleFunc("/solidarity-transport/bookings/{bookingid}", applicationHandler.SolidarityTransportBookingDisplay)
|
|
||||||
application.HandleFunc("/solidarity-transport/bookings/{bookingid}/confirm", applicationHandler.SolidarityTransportBookingStatus("confirm"))
|
|
||||||
application.HandleFunc("/solidarity-transport/bookings/{bookingid}/cancel", applicationHandler.SolidarityTransportBookingStatus("cancel"))
|
|
||||||
application.HandleFunc("/solidarity-transport/bookings/{bookingid}/waitconfirmation", applicationHandler.SolidarityTransportBookingStatus("waitconfirmation"))
|
|
||||||
application.HandleFunc("/organized-carpool/", applicationHandler.OrganizedCarpoolOverview)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/create", applicationHandler.OrganizedCarpoolCreateDriver)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/update", applicationHandler.OrganizedCarpoolUpdateDriver)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/trips", applicationHandler.OrganizedCarpoolAddTrip)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/archive", applicationHandler.OrganizedCarpoolArchiveDriver)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/unarchive", applicationHandler.OrganizedCarpoolUnarchiveDriver)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/documents", applicationHandler.OrganizedCarpoolDriverDocuments)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/documents/{document}", applicationHandler.OrganizedCarpoolDocumentDownload)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/trips/{tripid}/delete", applicationHandler.OrganizedCarpoolDeleteTrip)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}", applicationHandler.OrganizedCarpoolDriverDisplay)
|
|
||||||
application.HandleFunc("/organized-carpool/drivers/{driverid}/journeys/{journeyid}", applicationHandler.OrganizedCarpoolJourney)
|
|
||||||
application.HandleFunc("/organized-carpool/bookings/{bookingid}", applicationHandler.OrganizedCarpoolBookingDisplay)
|
|
||||||
application.HandleFunc("/organized-carpool/bookings/{bookingid}/confirm", applicationHandler.OrganizedCarpoolBookingStatus("confirm"))
|
|
||||||
application.HandleFunc("/organized-carpool/bookings/{bookingid}/cancel", applicationHandler.OrganizedCarpoolBookingStatus("cancel"))
|
|
||||||
application.HandleFunc("/organized-carpool/bookings/{bookingid}/waitconfirmation", applicationHandler.OrganizedCarpoolBookingStatus("waitconfirmation"))
|
|
||||||
application.HandleFunc("/vehicles/", applicationHandler.VehiclesSearch)
|
|
||||||
application.HandleFunc("/vehicles/bookings/", applicationHandler.VehiclesBookingsList)
|
|
||||||
application.HandleFunc("/vehicles/bookings/{bookingid}", applicationHandler.VehicleBookingDisplay)
|
|
||||||
application.HandleFunc("/vehicles/v/{vehicleid}/b/{beneficiaryid}", applicationHandler.Book)
|
|
||||||
application.HandleFunc("/vehicles/bookings/{bookingid}/documents/{document}", applicationHandler.BookingDocumentDownload)
|
|
||||||
application.HandleFunc("/vehicles-management/", applicationHandler.VehiclesManagementOverview)
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/add", applicationHandler.VehiclesFleetAdd)
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}", applicationHandler.VehiclesFleetDisplay)
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}/unavailability", applicationHandler.VehiclesFleetMakeUnavailable)
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}/update", applicationHandler.VehiclesFleetUpdate)
|
|
||||||
application.HandleFunc("/vehicles-management/bookings/", applicationHandler.VehiclesManagementBookingsList)
|
|
||||||
application.HandleFunc("/vehicles-management/bookings/{bookingid}", applicationHandler.VehicleManagementBookingDisplay)
|
|
||||||
application.HandleFunc("/vehicles-management/bookings/{bookingid}/change-vehicle", applicationHandler.VehicleManagementBookingChangeVehicle)
|
|
||||||
/////////////////////////////////////Remove booking vehicle/////////////////////////////////////////
|
|
||||||
application.HandleFunc("/vehicles-management/bookings/{bookingid}/delete", applicationHandler.UnbookingVehicle)
|
|
||||||
// application.HandleFunc("/vehicles-management/bookings/{bookingid}/delete", applicationHandler.DeleteBooking)
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
application.HandleFunc("/vehicles-management/bookings/{bookingid}/documents/{document}", applicationHandler.BookingDocumentDownload)
|
|
||||||
application.HandleFunc("/agenda/", applicationHandler.AgendaHome)
|
|
||||||
application.HandleFunc("/agenda/history", applicationHandler.AgendaHistory)
|
|
||||||
application.HandleFunc("/agenda/create-event", applicationHandler.AgendaCreateEvent)
|
|
||||||
application.HandleFunc("/agenda/{eventid}", applicationHandler.AgendaDisplayEvent)
|
|
||||||
///////////////////////////////Code to modify event///////////////////////
|
|
||||||
application.HandleFunc("/agenda/{eventid}/update", applicationHandler.AgendaUpdateEvent)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/delete", applicationHandler.AgendaDeleteEvent)
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////
|
|
||||||
application.HandleFunc("/agenda/{eventid}/subscribe", applicationHandler.AgendaSubscribeEvent)
|
|
||||||
application.HandleFunc("/directory/", applicationHandler.DirectoryHome)
|
|
||||||
|
|
||||||
application.HandleFunc("/group/settings", applicationHandler.GroupSettingsDisplay)
|
|
||||||
application.HandleFunc("/group/settings/invite-member", applicationHandler.GroupSettingsInviteMember)
|
|
||||||
|
|
||||||
application.HandleFunc("/journeys/groups_covoiturage", applicationHandler.GroupsGestion)
|
|
||||||
application.HandleFunc("/journeys/groups_covoiturage/create", applicationHandler.CreateGroup)
|
|
||||||
application.HandleFunc("/journeys/groups_covoiturage/create/{groupid}", applicationHandler.DisplayGroupCovoiturage)
|
|
||||||
application.HandleFunc("/journeys/groups_covoiturage/create/{id}/{groupid}/{memberid}", applicationHandler.UpdateGroupCovoiturage)
|
|
||||||
|
|
||||||
application.HandleFunc("/sms/send", applicationHandler.SendSMS)
|
|
||||||
|
|
||||||
application.HandleFunc("/support/", applicationHandler.SupportSend)
|
|
||||||
|
|
||||||
appGroup := application.PathPrefix("/group_module").Subrouter()
|
|
||||||
appGroup.HandleFunc("/", applicationHandler.Groups)
|
|
||||||
appGroup.HandleFunc("/groups", applicationHandler.CreateGroupModule)
|
|
||||||
appGroup.HandleFunc("/groups/{groupid}", applicationHandler.DisplayGroupModule)
|
|
||||||
|
|
||||||
// TODO Subrouters with middlewares checking security for each module ?
|
|
||||||
application.Use(idp.Middleware)
|
|
||||||
application.Use(idp.GroupsMiddleware)
|
|
||||||
|
|
||||||
appAdmin := application.PathPrefix("/administration").Subrouter()
|
|
||||||
appAdmin.HandleFunc("/", applicationHandler.Administration)
|
|
||||||
appAdmin.HandleFunc("/groups/", applicationHandler.AdministrationCreateGroup)
|
|
||||||
appAdmin.HandleFunc("/groups/{groupid}", applicationHandler.AdministrationGroupDisplay)
|
|
||||||
appAdmin.HandleFunc("/groups/{groupid}/invite-admin", applicationHandler.AdministrationGroupInviteAdmin)
|
|
||||||
appAdmin.HandleFunc("/groups/{groupid}/invite-member", applicationHandler.AdministrationGroupInviteMember)
|
|
||||||
// add statistiques
|
|
||||||
appAdmin.HandleFunc("/stats/vehicles", applicationHandler.AdminStatVehicles)
|
|
||||||
appAdmin.HandleFunc("/stats/bookings", applicationHandler.AdminStatBookings)
|
|
||||||
appAdmin.HandleFunc("/stats/beneficaires", applicationHandler.AdminStatBeneficaires)
|
|
||||||
appAdmin.HandleFunc("/stats/events", applicationHandler.AdminStatEvents)
|
|
||||||
|
|
||||||
/////////////////////////////////////Delete subscriber///////////////////////////////////////////////
|
|
||||||
application.HandleFunc("/agenda/{eventid}/{subscribeid}/delete", applicationHandler.AgendaDeleteSubscribeEvent)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/history", applicationHandler.AgendaHistoryEvent)
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////Add documents in event////////////////////////
|
|
||||||
application.HandleFunc("/agenda/{eventid}/documents", applicationHandler.EventDocuments)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/documents/{document}", applicationHandler.EventDocumentDownload)
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////Diag in event////////////////////////
|
|
||||||
// application.HandleFunc("/agenda/{eventid}/create-diag", applicationHandler.DiagsCreateDiag)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/diags", applicationHandler.DiagsHome)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/diags/{diagid}", applicationHandler.DiagsDisplayDiag)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/diags/history", applicationHandler.DiagsHistory)
|
|
||||||
application.HandleFunc("/agenda/{eventid}/diags/{diagid}/history", applicationHandler.DiagsHistoryDiag)
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////Diag in benefeciaries////////////////////////
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/create-diag", applicationHandler.BeneficiariesCreateDiag)
|
|
||||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/diags/{diagid}", applicationHandler.DiagsDisplayDiag)
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////Diag in vehicules////////////////////////
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}/create-diag", applicationHandler.VehiclesCreateDiag)
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}/diags", applicationHandler.DiagsHome)
|
|
||||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}/diags/{diagid}", applicationHandler.DiagsDisplayDiag)
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////Diag in bookings////////////////////////
|
|
||||||
application.HandleFunc("/vehicles-management/bookings/{bookingid}/create-diag", applicationHandler.BookingsCreateDiag)
|
|
||||||
application.HandleFunc("/vehicles/bookings/{vehicleid}/diags", applicationHandler.DiagsHome)
|
|
||||||
application.HandleFunc("/vehicles/bookings/{bookingid}/create-diag", applicationHandler.VehicleBookingsCreateDiag)
|
|
||||||
//////////////////Diags////////////////////////
|
|
||||||
application.HandleFunc("/diags/", applicationHandler.DiagsHome)
|
|
||||||
application.HandleFunc("/diags/{diagid}/documents", applicationHandler.DiagsDocuments)
|
|
||||||
application.HandleFunc("/diags/{diagid}/documents/{document}", applicationHandler.DiagsDocumentDownload)
|
|
||||||
application.HandleFunc("/diags/{diagid}", applicationHandler.DiagsDisplayDiag)
|
|
||||||
application.HandleFunc("/diags/{diagid}/update", applicationHandler.DiagUpdate)
|
|
||||||
application.HandleFunc("/diags/{diagid}/delete", applicationHandler.DiagDelete)
|
|
||||||
|
|
||||||
export := r.PathPrefix("/exports").Subrouter()
|
|
||||||
export.HandleFunc("/fleets/bookings", exportsHandler.AllBookings)
|
|
||||||
export.HandleFunc("/fleets/bookings/{groupid}", exportsHandler.Bookings)
|
|
||||||
export.HandleFunc("/agenda/subscriptions", exportsHandler.Agenda("allEvents"))
|
|
||||||
export.HandleFunc("/agenda/{eventid}", exportsHandler.Agenda("oneEvent"))
|
|
||||||
export.Use(idp.Middleware)
|
|
||||||
export.Use(idp.GroupsMiddleware)
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Handler: r,
|
|
||||||
Addr: address,
|
|
||||||
WriteTimeout: 30 * time.Second,
|
|
||||||
ReadTimeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("service_name", service_name).Str("address", address).Msg("Running HTTP server")
|
|
||||||
|
|
||||||
log.Fatal().Err(srv.ListenAndServe()).Msg("Failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func redirectApp(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func trackPage(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
log.Trace().Str("requested_uri", r.RequestURI).Msg("New request")
|
|
||||||
next.ServeHTTP(w, r.WithContext(r.Context()))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func (renderer *Renderer) AdminStatVehicles(w http.ResponseWriter, r *http.Reque
|
||||||
renderer.Render("vehicles_state", w, r, files, state)
|
renderer.Render("vehicles_state", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) AdminStatBookings(w http.ResponseWriter, r *http.Request, vehicles map[string]fleetsstorage.Vehicle, bookings []fleetsstorage.Booking, admingroups map[string]any, beneficiaries map[string]any) {
|
func (renderer *Renderer) AdminStatBookings(w http.ResponseWriter, r *http.Request, vehicles map[string]fleetsstorage.Vehicle, bookings []fleetsstorage.Booking, admingroups map[string]any, beneficiaries map[string]any, filters map[string]string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.administration.bookings_list.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.administration.bookings_list.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, administrationMenu)
|
state := NewState(r, renderer.ThemeConfig, administrationMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
|
|
@ -108,6 +108,7 @@ func (renderer *Renderer) AdminStatBookings(w http.ResponseWriter, r *http.Reque
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
"admingroups": admingroups,
|
"admingroups": admingroups,
|
||||||
"beneficiaries_map": beneficiaries,
|
"beneficiaries_map": beneficiaries,
|
||||||
|
"filters": filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("bookings_stats", w, r, files, state)
|
renderer.Render("bookings_stats", w, r, files, state)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ type BeneficiariesListState struct {
|
||||||
Count int `json:"count"`
|
Count int `json:"count"`
|
||||||
CacheId string `json:"cache_id"`
|
CacheId string `json:"cache_id"`
|
||||||
Beneficiaries []mobilityaccountsstorage.Account `json:"beneficiaries"`
|
Beneficiaries []mobilityaccountsstorage.Account `json:"beneficiaries"`
|
||||||
|
Archived bool `json:"archived"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s BeneficiariesListState) JSON() template.JS {
|
func (s BeneficiariesListState) JSON() template.JS {
|
||||||
|
|
@ -29,7 +30,7 @@ func (s BeneficiariesListState) JSONWithLimits(a int, b int) template.JS {
|
||||||
return s.JSON()
|
return s.JSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Request, accounts []mobilityaccountsstorage.Account, cacheid string) {
|
func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Request, accounts []mobilityaccountsstorage.Account, cacheid string, archived bool) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.list.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.list.files")
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
||||||
|
|
@ -37,6 +38,7 @@ func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Reque
|
||||||
Count: len(accounts),
|
Count: len(accounts),
|
||||||
CacheId: cacheid,
|
CacheId: cacheid,
|
||||||
Beneficiaries: accounts,
|
Beneficiaries: accounts,
|
||||||
|
Archived: archived,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("beneficiaries_list", w, r, files, state)
|
renderer.Render("beneficiaries_list", w, r, files, state)
|
||||||
|
|
@ -58,7 +60,7 @@ type BeneficiariesDisplayState struct {
|
||||||
Beneficiary any
|
Beneficiary any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) BeneficiaryDisplay(w http.ResponseWriter, r *http.Request, beneficiary any, bookings []fleetsstorage.Booking, organizations []any, beneficiaries_file_types []string, file_types_map map[string]string, documents any, event interface{}, diags []any, solidarityTransportStats any, walletBalance float64) {
|
func (renderer *Renderer) BeneficiaryDisplay(w http.ResponseWriter, r *http.Request, beneficiary any, bookings []fleetsstorage.Booking, organizations []any, beneficiaries_file_types []string, file_types_map map[string]string, documents any, event interface{}, solidarityTransportStats any, solidarityTransportBookings any, solidarityDriversMap any, organizedCarpoolStats any, organizedCarpoolBookings any, organizedCarpoolDriversMap any, walletBalance float64, tab string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.display.files")
|
||||||
profileFields := renderer.GlobalConfig.Get("modules.beneficiaries.profile_optional_fields")
|
profileFields := renderer.GlobalConfig.Get("modules.beneficiaries.profile_optional_fields")
|
||||||
|
|
||||||
|
|
@ -71,10 +73,16 @@ func (renderer *Renderer) BeneficiaryDisplay(w http.ResponseWriter, r *http.Requ
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"organizations": organizations,
|
"organizations": organizations,
|
||||||
"event": event,
|
"event": event,
|
||||||
"diags": diags,
|
|
||||||
"solidarity_transport_stats": solidarityTransportStats,
|
"solidarity_transport_stats": solidarityTransportStats,
|
||||||
|
"solidarity_transport_bookings": solidarityTransportBookings,
|
||||||
|
"solidarity_transport_drivers_map": solidarityDriversMap,
|
||||||
|
"organized_carpool_stats": organizedCarpoolStats,
|
||||||
|
"organized_carpool_bookings": organizedCarpoolBookings,
|
||||||
|
"organized_carpool_drivers_map": organizedCarpoolDriversMap,
|
||||||
"profile_optional_fields": profileFields,
|
"profile_optional_fields": profileFields,
|
||||||
"wallet_balance": walletBalance,
|
"wallet_balance": walletBalance,
|
||||||
|
"tab": tab,
|
||||||
|
"search_view": renderer.GlobalConfig.GetString("modules.journeys.search_view"),
|
||||||
}
|
}
|
||||||
renderer.Render("beneficiaries_display", w, r, files, state)
|
renderer.Render("beneficiaries_display", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ import (
|
||||||
|
|
||||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||||
fleetstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
fleetstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dashboardMenu = "dashboard"
|
const dashboardMenu = "dashboard"
|
||||||
|
|
||||||
func (renderer *Renderer) Dashboard(w http.ResponseWriter, r *http.Request, accounts []any, nbaccounts int, count_members int, events []agendastorage.Event, fleets []fleetstorage.Booking) {
|
func (renderer *Renderer) Dashboard(w http.ResponseWriter, r *http.Request, accounts any, nbaccounts int, count_members int, events []agendastorage.Event, fleets []fleetstorage.Booking, solidarityDrivers []mobilityaccountsstorage.Account, organizedCarpoolDrivers []mobilityaccountsstorage.Account, driverAddressGeo string, enrichedGeoFilters []map[string]string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.dashboard.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.dashboard.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, dashboardMenu)
|
state := NewState(r, renderer.ThemeConfig, dashboardMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
|
|
@ -20,6 +21,13 @@ func (renderer *Renderer) Dashboard(w http.ResponseWriter, r *http.Request, acco
|
||||||
"count_members": count_members,
|
"count_members": count_members,
|
||||||
"events": events,
|
"events": events,
|
||||||
"fleets": fleets,
|
"fleets": fleets,
|
||||||
|
"solidarity_drivers": solidarityDrivers,
|
||||||
|
"organized_carpool_drivers": organizedCarpoolDrivers,
|
||||||
|
"geography_filters_enabled": renderer.GlobalConfig.GetBool("geography.filters.enabled"),
|
||||||
|
"geography_filters_list": enrichedGeoFilters,
|
||||||
|
"filters": map[string]any{
|
||||||
|
"driver_address_geo": driverAddressGeo,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("dashboard", w, r, files, state)
|
renderer.Render("dashboard", w, r, files, state)
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
package renderer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
diagsstorage "git.coopgo.io/coopgo-platform/diags/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
const diagsMenu = "diags"
|
|
||||||
|
|
||||||
func (renderer *Renderer) DiagsHome(w http.ResponseWriter, r *http.Request, diags []diagsstorage.Diag, groups map[string]any) {
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.diags.list.files")
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"diags": diags,
|
|
||||||
"groups": groups,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.Render("diags home", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) DiagsHistory(w http.ResponseWriter, r *http.Request, diags []diagsstorage.Diag) {
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.diags.history.files")
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"diags": diags,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.Render("diags history", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) BeneficiariesCreateDiag(w http.ResponseWriter, r *http.Request, beneficiary string, diags_file_types []string, file_types_map map[string]string, documents any) {
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.create_diag.files")
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"beneficiary": beneficiary,
|
|
||||||
"diags_file_types": diags_file_types,
|
|
||||||
"file_types_map": file_types_map,
|
|
||||||
"documents": documents,
|
|
||||||
}
|
|
||||||
renderer.Render("diag create for beneficiary", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesCreateDiag(w http.ResponseWriter, r *http.Request, vehicle string) {
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.create_vehicle_diag.files")
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"vehicle": vehicle,
|
|
||||||
}
|
|
||||||
renderer.Render("diag create for vehicle", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) BookingsCreateDiag(w http.ResponseWriter, r *http.Request, booking string) {
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.create_booking_diag.files")
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"booking": booking,
|
|
||||||
}
|
|
||||||
renderer.Render("diag create for booking", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) VehicleBookingsCreateDiag(w http.ResponseWriter, r *http.Request, booking string) {
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.create_booking_diag.files")
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"booking": booking,
|
|
||||||
}
|
|
||||||
renderer.Render("diag create for booking", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) DiagsDisplayDiag(w http.ResponseWriter, r *http.Request, diag any, diags_file_types []string, file_types_map map[string]string, documents any) {
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.diags.display_diag.files")
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"diag": diag,
|
|
||||||
"diags_file_types": diags_file_types,
|
|
||||||
"file_types_map": file_types_map,
|
|
||||||
"documents": documents,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.Render("diags create diag", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) DiagsHistoryDiag(w http.ResponseWriter, r *http.Request, diag any) {
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.diags.history_diag.files")
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"diag": diag,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.Render("diags history diag", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) DiagUpdate(w http.ResponseWriter, r *http.Request, diag any) {
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.diags.update.files")
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"diag": diag,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.Render("diag_update", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (renderer *Renderer) DiagDelete(w http.ResponseWriter, r *http.Request, diag any) {
|
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.diags.delete.files")
|
|
||||||
state := NewState(r, renderer.ThemeConfig, diagsMenu)
|
|
||||||
|
|
||||||
state.ViewState = map[string]any{
|
|
||||||
"diag": diag,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.Render("diag_deleteDiag", w, r, files, state)
|
|
||||||
}
|
|
||||||
|
|
@ -5,12 +5,13 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
validatedprofile "git.coopgo.io/coopgo-apps/parcoursmob/utils/validated-profile"
|
validatedprofile "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/validated-profile"
|
||||||
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
@ -151,6 +152,24 @@ func strval(v interface{}) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSEscape escapes a string for safe use in JavaScript
|
||||||
|
func JSEscape(s string) template.JS {
|
||||||
|
return template.JS(template.JSEscapeString(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGuaranteedTripMotivation checks if a motivation is a guaranteed trip
|
||||||
|
func IsGuaranteedTripMotivation(globalConfig *viper.Viper) func(string) bool {
|
||||||
|
return func(motivation string) bool {
|
||||||
|
guaranteedMotivations := globalConfig.GetStringSlice("modules.solidarity_transport.guaranteed_trip_motivations")
|
||||||
|
for _, m := range guaranteedMotivations {
|
||||||
|
if m == motivation {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetTemplateFuncMap returns the common template functions for rendering
|
// GetTemplateFuncMap returns the common template functions for rendering
|
||||||
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fileStorage filestorage.FileStorage) template.FuncMap {
|
||||||
return template.FuncMap{
|
return template.FuncMap{
|
||||||
|
|
@ -162,14 +181,17 @@ func GetTemplateFuncMap(group groupsstorage.Group, globalConfig *viper.Viper, fi
|
||||||
"json": JSON,
|
"json": JSON,
|
||||||
"rawjson": RawJSON,
|
"rawjson": RawJSON,
|
||||||
"unescapeHTML": UnescapeHTML,
|
"unescapeHTML": UnescapeHTML,
|
||||||
|
"jsEscape": JSEscape,
|
||||||
"walkingLength": WalkingLength,
|
"walkingLength": WalkingLength,
|
||||||
"divideFloat64": Divide[float64],
|
"divideFloat64": Divide[float64],
|
||||||
"divideInt": Divide[int],
|
"divideInt": Divide[int],
|
||||||
"typeOf": reflect.TypeOf,
|
"typeOf": reflect.TypeOf,
|
||||||
"shortDuration": ShortDuration,
|
"shortDuration": ShortDuration,
|
||||||
|
"round2": Round2,
|
||||||
"beneficiaryValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.beneficiaries.validated_profile")),
|
"beneficiaryValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.beneficiaries.validated_profile")),
|
||||||
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
"solidarityDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.solidarity_transport.drivers.validated_profile")),
|
||||||
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
"carpoolDriverValidatedProfile": validatedprofile.ValidateProfile(globalConfig.Sub("modules.organized_carpool.drivers.validated_profile")),
|
||||||
|
"isGuaranteedTripMotivation": IsGuaranteedTripMotivation(globalConfig),
|
||||||
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
"beneficiaryDocuments": func(id string) []filestorage.FileInfo {
|
||||||
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
return fileStorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + id)
|
||||||
},
|
},
|
||||||
|
|
@ -211,3 +233,25 @@ func ShortDuration(d interface{}) string {
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Round2 rounds a float64 to 2 decimal places to avoid floating point issues
|
||||||
|
func Round2(value interface{}) float64 {
|
||||||
|
var f float64
|
||||||
|
switch v := value.(type) {
|
||||||
|
case float64:
|
||||||
|
f = v
|
||||||
|
case *float64:
|
||||||
|
if v != nil {
|
||||||
|
f = *v
|
||||||
|
}
|
||||||
|
case float32:
|
||||||
|
f = float64(v)
|
||||||
|
case int:
|
||||||
|
f = float64(v)
|
||||||
|
case int64:
|
||||||
|
f = float64(v)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return math.Round(f*100) / 100
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func (s BeneficiariesCovoiturage) JSONWithLimits(a int, b int) template.JS {
|
||||||
return s.JSON()
|
return s.JSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request, carpools []*geojson.FeatureCollection, transitjourneys any, vehicles any, searched bool, departure any, destination any, departuredate string, departuretime string, driverJourneys any, solidarityDrivers any, organizedCarpools any, beneficiaries any, kbData any, passengerid string) {
|
func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request, carpools []*geojson.FeatureCollection, transitjourneys any, vehicles any, searched bool, departure any, destination any, departuredate string, departuretime string, driverJourneys any, solidarityDrivers any, organizedCarpools any, beneficiaries any, kbData any, passengerid string, savedSearches any, beneficiariesMap any) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.journeys.search.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.journeys.search.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, journeysMenu)
|
state := NewState(r, renderer.ThemeConfig, journeysMenu)
|
||||||
journeyTabs := renderer.ThemeConfig.Get("journey_tabs")
|
journeyTabs := renderer.ThemeConfig.Get("journey_tabs")
|
||||||
|
|
@ -53,10 +53,13 @@ func (renderer *Renderer) JourneysSearch(w http.ResponseWriter, r *http.Request,
|
||||||
"driver_journeys": driverJourneys,
|
"driver_journeys": driverJourneys,
|
||||||
"solidarity_drivers": solidarityDrivers,
|
"solidarity_drivers": solidarityDrivers,
|
||||||
"querystring": r.URL.RawQuery,
|
"querystring": r.URL.RawQuery,
|
||||||
"beneficiaries": beneficiaries,
|
"beneficiaries": beneficiariesMap,
|
||||||
|
"beneficiaries_list": beneficiaries,
|
||||||
"kb_data": kbData,
|
"kb_data": kbData,
|
||||||
"passengerid": passengerid,
|
"passengerid": passengerid,
|
||||||
"journey_tabs": journeyTabs,
|
"journey_tabs": journeyTabs,
|
||||||
|
"saved_searches": savedSearches,
|
||||||
|
"search_view": renderer.GlobalConfig.GetString("modules.journeys.search_view"),
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("journeys", w, r, files, state)
|
renderer.Render("journeys", w, r, files, state)
|
||||||
|
|
@ -156,3 +159,28 @@ func (renderer *Renderer) UpdateGroupCovoiturage(w http.ResponseWriter, r *http.
|
||||||
}
|
}
|
||||||
renderer.Render("journeys", w, r, files, state)
|
renderer.Render("journeys", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (renderer *Renderer) JourneysSearchCompact(w http.ResponseWriter, r *http.Request, carpools []*geojson.FeatureCollection, transitjourneys any, vehicles any, searched bool, departure any, destination any, departuredate string, departuretime string, driverJourneys any, solidarityDrivers any, organizedCarpools any, kbData any, passengerid string) {
|
||||||
|
files := renderer.ThemeConfig.GetStringSlice("views.journeys.search_compact.files")
|
||||||
|
vehicleOptionalFields := renderer.GlobalConfig.Get("modules.fleets.vehicle_optional_fields")
|
||||||
|
|
||||||
|
state := NewState(r, renderer.ThemeConfig, journeysMenu)
|
||||||
|
state.ViewState = map[string]any{
|
||||||
|
"searched": searched,
|
||||||
|
"departuredate": departuredate,
|
||||||
|
"departuretime": departuretime,
|
||||||
|
"departure": departure,
|
||||||
|
"destination": destination,
|
||||||
|
"journeys": transitjourneys,
|
||||||
|
"carpools": carpools,
|
||||||
|
"organized_carpools": organizedCarpools,
|
||||||
|
"vehicles": vehicles,
|
||||||
|
"vehicle_optional_fields": vehicleOptionalFields,
|
||||||
|
"driver_journeys": driverJourneys,
|
||||||
|
"solidarity_drivers": solidarityDrivers,
|
||||||
|
"kb_data": kbData,
|
||||||
|
"passengerid": passengerid,
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.Render("journeys", w, r, files, state)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,33 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"git.coopgo.io/coopgo-platform/payments/pricing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const organizedCarpoolMenu = "organized_carpool"
|
const organizedCarpoolMenu = "organized_carpool"
|
||||||
|
|
||||||
func (renderer *Renderer) OrganizedCarpoolOverview(w http.ResponseWriter, r *http.Request, drivers any, driversMap any, passengersMap any, bookings any) {
|
func (renderer *Renderer) OrganizedCarpoolOverview(w http.ResponseWriter, r *http.Request, drivers any, driversMap any, passengersMap any, bookings any, bookingsHistory any, filters map[string]any, histFilters map[string]any, tab string, enrichedGeoFilters []map[string]string, archived bool) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.overview.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.overview.files")
|
||||||
|
tripsItemsPerPage := renderer.GlobalConfig.GetInt("modules.organized_carpool.pagination.trips_items_per_page")
|
||||||
|
driversItemsPerPage := renderer.GlobalConfig.GetInt("modules.organized_carpool.pagination.drivers_items_per_page")
|
||||||
|
|
||||||
|
geoFiltersEnabled := len(enrichedGeoFilters) > 0
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"drivers": drivers,
|
"drivers": drivers,
|
||||||
"drivers_map": driversMap,
|
"drivers_map": driversMap,
|
||||||
"passengers_map": passengersMap,
|
"passengers_map": passengersMap,
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
|
"bookings_history": bookingsHistory,
|
||||||
|
"filters": filters,
|
||||||
|
"hist_filters": histFilters,
|
||||||
|
"tab": tab,
|
||||||
|
"trips_items_per_page": tripsItemsPerPage,
|
||||||
|
"drivers_items_per_page": driversItemsPerPage,
|
||||||
|
"geography_filters_enabled": geoFiltersEnabled,
|
||||||
|
"geography_filters_list": enrichedGeoFilters,
|
||||||
|
"archived": archived,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("organized carpool overview", w, r, files, state)
|
renderer.Render("organized carpool overview", w, r, files, state)
|
||||||
|
|
@ -39,7 +54,7 @@ func (renderer *Renderer) OrganizedCarpoolUpdateDriver(w http.ResponseWriter, r
|
||||||
renderer.Render("organized carpool driver update", w, r, files, state)
|
renderer.Render("organized carpool driver update", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) OrganizedCarpoolDriverDisplay(w http.ResponseWriter, r *http.Request, driver mobilityaccountsstorage.Account, trips any, documents any) {
|
func (renderer *Renderer) OrganizedCarpoolDriverDisplay(w http.ResponseWriter, r *http.Request, driver mobilityaccountsstorage.Account, trips any, documents any, bookings any, beneficiariesMap any, stats map[string]any, walletBalance float64, tab string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.driver_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.driver_display.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
||||||
|
|
||||||
|
|
@ -50,22 +65,31 @@ func (renderer *Renderer) OrganizedCarpoolDriverDisplay(w http.ResponseWriter, r
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
"trips": trips,
|
"trips": trips,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
|
"bookings": bookings,
|
||||||
|
"beneficiaries_map": beneficiariesMap,
|
||||||
|
"stats": stats,
|
||||||
"drivers_file_types": drivers_file_types,
|
"drivers_file_types": drivers_file_types,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
|
"wallet_balance": walletBalance,
|
||||||
|
"tab": tab,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("organized carpool driver display", w, r, files, state)
|
renderer.Render("organized carpool driver display", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) OrganizedCarpoolJourney(w http.ResponseWriter, r *http.Request, journey any, driver any, passenger any, beneficiaries any) {
|
func (renderer *Renderer) OrganizedCarpoolJourney(w http.ResponseWriter, r *http.Request, journey any, driver any, passenger any, beneficiaries any, passengerWalletBalance float64, pricingResult map[string]pricing.Price) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.journey.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.organized_carpool.journey.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
bookingMotivations := renderer.GlobalConfig.Get("modules.organized_carpool.booking_motivations")
|
||||||
|
state := NewState(r, renderer.ThemeConfig, organizedCarpoolMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
"passenger": passenger,
|
"passenger": passenger,
|
||||||
"beneficiaries": beneficiaries,
|
"beneficiaries": beneficiaries,
|
||||||
"journey": journey,
|
"journey": journey,
|
||||||
"config": renderer.GlobalConfig,
|
"config": renderer.GlobalConfig,
|
||||||
|
"booking_motivations": bookingMotivations,
|
||||||
|
"passenger_wallet_balance": passengerWalletBalance,
|
||||||
|
"pricing_result": pricingResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("organized carpool journey", w, r, files, state)
|
renderer.Render("organized carpool journey", w, r, files, state)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/icons"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/icons"
|
||||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
cache "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
xlsxrenderer "git.coopgo.io/coopgo-apps/parcoursmob/renderer/xlsx"
|
||||||
"git.coopgo.io/coopgo-platform/emailing"
|
"git.coopgo.io/coopgo-platform/emailing"
|
||||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
|
@ -21,6 +22,7 @@ type Renderer struct {
|
||||||
ThemeConfig *viper.Viper
|
ThemeConfig *viper.Viper
|
||||||
Mailer *emailing.Mailer
|
Mailer *emailing.Mailer
|
||||||
FileStorage cache.FileStorage
|
FileStorage cache.FileStorage
|
||||||
|
XLSX *xlsxrenderer.XLSXRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRenderer(global *viper.Viper, templates_dir string, filestorage cache.FileStorage) *Renderer {
|
func NewRenderer(global *viper.Viper, templates_dir string, filestorage cache.FileStorage) *Renderer {
|
||||||
|
|
@ -36,6 +38,7 @@ func NewRenderer(global *viper.Viper, templates_dir string, filestorage cache.Fi
|
||||||
GlobalConfig: global,
|
GlobalConfig: global,
|
||||||
ThemeConfig: theme,
|
ThemeConfig: theme,
|
||||||
FileStorage: filestorage,
|
FileStorage: filestorage,
|
||||||
|
XLSX: xlsxrenderer.NewXLSXRenderer(global),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,6 +142,7 @@ func NewState(r *http.Request, themeConfig *viper.Viper, menuState string) Rende
|
||||||
},
|
},
|
||||||
|
|
||||||
Menu: menu,
|
Menu: menu,
|
||||||
|
ActiveMenu: menuState,
|
||||||
|
|
||||||
// DEPRECATED
|
// DEPRECATED
|
||||||
MenuItems: []MenuItem{
|
MenuItems: []MenuItem{
|
||||||
|
|
@ -232,14 +236,6 @@ func NewState(r *http.Request, themeConfig *viper.Viper, menuState string) Rende
|
||||||
Icon: "hero:outline/document-text",
|
Icon: "hero:outline/document-text",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/*if modules["diags"] != nil && modules["diags"].(bool) {
|
|
||||||
ls.MenuItems = append(ls.MenuItems, MenuItem{
|
|
||||||
Title: "Diagnostics",
|
|
||||||
Link: "/app/diags/",
|
|
||||||
Active: menuState == diagsMenu,
|
|
||||||
Icon: "hero:outline/document-text",
|
|
||||||
})
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return RenderState{
|
return RenderState{
|
||||||
IconSet: icons.NewIconSet(iconset),
|
IconSet: icons.NewIconSet(iconset),
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,21 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"git.coopgo.io/coopgo-platform/payments/pricing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const solidarityTransportMenu = "solidarity_transport"
|
const solidarityTransportMenu = "solidarity_transport"
|
||||||
|
|
||||||
func (renderer *Renderer) SolidarityTransportOverview(w http.ResponseWriter, r *http.Request, drivers any, driversMap any, passengersMap any, bookings any, bookingsHistory any, filters any, hist_filters any, tab string) {
|
func (renderer *Renderer) SolidarityTransportOverview(w http.ResponseWriter, r *http.Request, drivers any, driversMap any, passengersMap any, bookings any, bookingsHistory any, filters any, hist_filters any, tab string, enrichedGeoFilters []map[string]string, archived bool) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.overview.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.overview.files")
|
||||||
|
tripsItemsPerPage := renderer.GlobalConfig.GetInt("modules.solidarity_transport.pagination.trips_items_per_page")
|
||||||
|
driversItemsPerPage := renderer.GlobalConfig.GetInt("modules.solidarity_transport.pagination.drivers_items_per_page")
|
||||||
|
|
||||||
|
guaranteedMotivations := renderer.GlobalConfig.GetStringSlice("modules.solidarity_transport.guaranteed_trip_motivations")
|
||||||
|
|
||||||
|
// Geography filters
|
||||||
|
geoFiltersEnabled := len(enrichedGeoFilters) > 0
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"drivers": drivers,
|
"drivers": drivers,
|
||||||
|
|
@ -20,6 +29,12 @@ func (renderer *Renderer) SolidarityTransportOverview(w http.ResponseWriter, r *
|
||||||
"filters": filters,
|
"filters": filters,
|
||||||
"hist_filters": hist_filters,
|
"hist_filters": hist_filters,
|
||||||
"tab": tab,
|
"tab": tab,
|
||||||
|
"trips_items_per_page": tripsItemsPerPage,
|
||||||
|
"drivers_items_per_page": driversItemsPerPage,
|
||||||
|
"guaranteed_trip_motivations": guaranteedMotivations,
|
||||||
|
"geography_filters_enabled": geoFiltersEnabled,
|
||||||
|
"geography_filters_list": enrichedGeoFilters,
|
||||||
|
"archived": archived,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("solidarity transport overview", w, r, files, state)
|
renderer.Render("solidarity transport overview", w, r, files, state)
|
||||||
|
|
@ -50,7 +65,7 @@ func (renderer *Renderer) SolidarityTransportUpdateDriver(w http.ResponseWriter,
|
||||||
renderer.Render("solidarity transport driver update", w, r, files, state)
|
renderer.Render("solidarity transport driver update", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) SolidarityTransportDriverDisplay(w http.ResponseWriter, r *http.Request, driver mobilityaccountsstorage.Account, availabilities any, documents any, bookings any, stats map[string]any, walletBalance float64) {
|
func (renderer *Renderer) SolidarityTransportDriverDisplay(w http.ResponseWriter, r *http.Request, driver mobilityaccountsstorage.Account, availabilities any, documents any, bookings any, beneficiariesMap any, stats map[string]any, walletBalance float64, tab string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.driver_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.driver_display.files")
|
||||||
profileFields := renderer.GlobalConfig.Get("modules.solidarity_transport.drivers.profile_optional_fields")
|
profileFields := renderer.GlobalConfig.Get("modules.solidarity_transport.drivers.profile_optional_fields")
|
||||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
||||||
|
|
@ -62,19 +77,22 @@ func (renderer *Renderer) SolidarityTransportDriverDisplay(w http.ResponseWriter
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
"availabilities": availabilities,
|
"availabilities": availabilities,
|
||||||
"bookings": bookings,
|
"bookings": bookings,
|
||||||
|
"beneficiaries_map": beneficiariesMap,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"drivers_file_types": drivers_file_types,
|
"drivers_file_types": drivers_file_types,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
"stats": stats,
|
"stats": stats,
|
||||||
"profile_optional_fields": profileFields,
|
"profile_optional_fields": profileFields,
|
||||||
"wallet_balance": walletBalance,
|
"wallet_balance": walletBalance,
|
||||||
|
"tab": tab,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("solidarity transport driver creation", w, r, files, state)
|
renderer.Render("solidarity transport driver creation", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) SolidarityTransportDriverJourney(w http.ResponseWriter, r *http.Request, driverJourney any, driver any, passenger any, beneficiaries any, passengerWalletBalance float64) {
|
func (renderer *Renderer) SolidarityTransportDriverJourney(w http.ResponseWriter, r *http.Request, driverJourney any, driver any, passenger any, beneficiaries any, passengerWalletBalance float64, pricingResult map[string]pricing.Price) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.driver_journey.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.driver_journey.files")
|
||||||
|
bookingMotivations := renderer.GlobalConfig.Get("modules.solidarity_transport.booking_motivations")
|
||||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
|
|
@ -83,6 +101,8 @@ func (renderer *Renderer) SolidarityTransportDriverJourney(w http.ResponseWriter
|
||||||
"driver_journey": driverJourney,
|
"driver_journey": driverJourney,
|
||||||
"config": renderer.GlobalConfig,
|
"config": renderer.GlobalConfig,
|
||||||
"passenger_wallet_balance": passengerWalletBalance,
|
"passenger_wallet_balance": passengerWalletBalance,
|
||||||
|
"pricing_result": pricingResult,
|
||||||
|
"booking_motivations": bookingMotivations,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("solidarity transport driver creation", w, r, files, state)
|
renderer.Render("solidarity transport driver creation", w, r, files, state)
|
||||||
|
|
@ -90,12 +110,14 @@ func (renderer *Renderer) SolidarityTransportDriverJourney(w http.ResponseWriter
|
||||||
|
|
||||||
func (renderer *Renderer) SolidarityTransportBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, driver any, passenger any, passengerWalletBalance float64) {
|
func (renderer *Renderer) SolidarityTransportBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, driver any, passenger any, passengerWalletBalance float64) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.booking_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.solidarity_transport.booking_display.files")
|
||||||
|
bookingMotivations := renderer.GlobalConfig.Get("modules.solidarity_transport.booking_motivations")
|
||||||
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
state := NewState(r, renderer.ThemeConfig, solidarityTransportMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"driver": driver,
|
"driver": driver,
|
||||||
"passenger": passenger,
|
"passenger": passenger,
|
||||||
"booking": booking,
|
"booking": booking,
|
||||||
"passenger_wallet_balance": passengerWalletBalance,
|
"passenger_wallet_balance": passengerWalletBalance,
|
||||||
|
"booking_motivations": bookingMotivations,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("booking display", w, r, files, state)
|
renderer.Render("booking display", w, r, files, state)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package renderer
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
)
|
)
|
||||||
|
|
@ -23,7 +23,7 @@ func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *h
|
||||||
renderer.Render("fleet overview", w, r, files, state)
|
renderer.Render("fleet overview", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, cacheid string) {
|
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account, bookings []fleetsstorage.Booking, cacheid string, filters map[string]string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
|
|
@ -31,6 +31,7 @@ func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter,
|
||||||
"vehicles_map": vehiclesMap,
|
"vehicles_map": vehiclesMap,
|
||||||
"drivers_map": driversMap,
|
"drivers_map": driversMap,
|
||||||
"cacheid": cacheid,
|
"cacheid": cacheid,
|
||||||
|
"filters": filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("fleet overview", w, r, files, state)
|
renderer.Render("fleet overview", w, r, files, state)
|
||||||
|
|
@ -38,21 +39,26 @@ func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter,
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesFleetAdd(w http.ResponseWriter, r *http.Request, vehicle_types []string) {
|
func (renderer *Renderer) VehiclesFleetAdd(w http.ResponseWriter, r *http.Request, vehicle_types []string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_add.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_add.files")
|
||||||
|
vehicleOptionalFields := renderer.GlobalConfig.Get("modules.fleets.vehicle_optional_fields")
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"vehicle_types": vehicle_types,
|
"vehicle_types": vehicle_types,
|
||||||
|
"vehicle_optional_fields": vehicleOptionalFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("fleet add vehicle", w, r, files, state)
|
renderer.Render("fleet add vehicle", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesFleetDisplay(w http.ResponseWriter, r *http.Request, vehicle any, beneficiaries any, diags []any) {
|
func (renderer *Renderer) VehiclesFleetDisplay(w http.ResponseWriter, r *http.Request, vehicle any, beneficiaries any) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_display.files")
|
||||||
|
vehicleOptionalFields := renderer.GlobalConfig.Get("modules.fleets.vehicle_optional_fields")
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"vehicle": vehicle,
|
"vehicle": vehicle,
|
||||||
"beneficiaries": beneficiaries,
|
"beneficiaries": beneficiaries,
|
||||||
"diags": diags,
|
"vehicle_optional_fields": vehicleOptionalFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("fleet display vehicle", w, r, files, state)
|
renderer.Render("fleet display vehicle", w, r, files, state)
|
||||||
|
|
@ -60,16 +66,19 @@ func (renderer *Renderer) VehiclesFleetDisplay(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
func (renderer *Renderer) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Request, vehicle any, vehicle_types []string) {
|
func (renderer *Renderer) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Request, vehicle any, vehicle_types []string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_update.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_update.files")
|
||||||
|
vehicleOptionalFields := renderer.GlobalConfig.Get("modules.fleets.vehicle_optional_fields")
|
||||||
|
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
"vehicle": vehicle,
|
"vehicle": vehicle,
|
||||||
"vehicle_types": vehicle_types,
|
"vehicle_types": vehicle_types,
|
||||||
|
"vehicle_optional_fields": vehicleOptionalFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("fleet display vehicle", w, r, files, state)
|
renderer.Render("fleet display vehicle", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, alternative_vehicles []any, diags []any) {
|
func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, alternative_vehicles []any) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
|
|
@ -80,7 +89,6 @@ func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
"alternative_vehicles": alternative_vehicles,
|
"alternative_vehicles": alternative_vehicles,
|
||||||
"diags": diags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("vehicles search", w, r, files, state)
|
renderer.Render("vehicles search", w, r, files, state)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package renderer
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
"git.coopgo.io/coopgo-platform/fleets/storage"
|
"git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
|
@ -53,7 +53,7 @@ func (renderer *Renderer) VehiclesSearch(w http.ResponseWriter, r *http.Request,
|
||||||
renderer.Render("vehicles search", w, r, files, state)
|
renderer.Render("vehicles search", w, r, files, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (renderer *Renderer) VehicleBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string, diags []any) {
|
func (renderer *Renderer) VehicleBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any, documents []filestorage.FileInfo, file_types_map map[string]string) {
|
||||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.booking_display.files")
|
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.booking_display.files")
|
||||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||||
state.ViewState = map[string]any{
|
state.ViewState = map[string]any{
|
||||||
|
|
@ -63,7 +63,6 @@ func (renderer *Renderer) VehicleBookingDisplay(w http.ResponseWriter, r *http.R
|
||||||
"group": group,
|
"group": group,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"file_types_map": file_types_map,
|
"file_types_map": file_types_map,
|
||||||
"diags": diags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.Render("vehicles search", w, r, files, state)
|
renderer.Render("vehicles search", w, r, files, state)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
package xlsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/gender"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *XLSXRenderer) OrganizedCarpoolBookings(w http.ResponseWriter, result *application.OrganizedCarpoolBookingsResult) {
|
||||||
|
// Create Excel spreadsheet
|
||||||
|
spreadsheet := r.NewSpreadsheet("Covoiturage solidaire")
|
||||||
|
|
||||||
|
// Build headers dynamically based on configuration
|
||||||
|
headers := []string{
|
||||||
|
"ID Réservation",
|
||||||
|
"Statut",
|
||||||
|
"Motif de réservation",
|
||||||
|
"Date de prise en charge",
|
||||||
|
"Heure de prise en charge",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add driver fields from config
|
||||||
|
driverOptionalFields := r.Config.Get("modules.organized_carpool.profile_optional_fields")
|
||||||
|
driverFields := []string{"last_name", "first_name", "email", "phone_number"}
|
||||||
|
driverHeaders := []string{"Conducteur - Nom", "Conducteur - Prénom", "Conducteur - Email", "Conducteur - Téléphone"}
|
||||||
|
|
||||||
|
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range driverOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
driverFields = append(driverFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
driverHeaders = append(driverHeaders, fmt.Sprintf("Conducteur - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, driverHeaders...)
|
||||||
|
|
||||||
|
// Add beneficiary fields from config
|
||||||
|
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
|
||||||
|
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
|
||||||
|
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
|
||||||
|
|
||||||
|
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range beneficiaryOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
beneficiaryFields = append(beneficiaryFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, beneficiaryHeaders...)
|
||||||
|
|
||||||
|
// Add journey information headers
|
||||||
|
headers = append(headers,
|
||||||
|
"Lieu de départ - Adresse",
|
||||||
|
"Destination - Adresse",
|
||||||
|
"Distance passager (km)",
|
||||||
|
"Durée trajet (minutes)",
|
||||||
|
"Prix passager",
|
||||||
|
"Devise prix passager",
|
||||||
|
"Compensation conducteur",
|
||||||
|
"Devise compensation",
|
||||||
|
)
|
||||||
|
|
||||||
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for _, booking := range result.Bookings {
|
||||||
|
driver := result.DriversMap[booking.Driver.Id]
|
||||||
|
beneficiary := result.BeneficiariesMap[booking.Passenger.Id]
|
||||||
|
|
||||||
|
row := []interface{}{}
|
||||||
|
|
||||||
|
// Booking information
|
||||||
|
row = append(row, booking.Id)
|
||||||
|
row = append(row, booking.Status.String())
|
||||||
|
|
||||||
|
// Motivation
|
||||||
|
motivation := ""
|
||||||
|
if booking.Motivation != nil {
|
||||||
|
motivation = *booking.Motivation
|
||||||
|
}
|
||||||
|
row = append(row, motivation)
|
||||||
|
|
||||||
|
// Journey date and time
|
||||||
|
row = append(row, booking.PassengerPickupDate.AsTime().Format("2006-01-02"))
|
||||||
|
row = append(row, booking.PassengerPickupDate.AsTime().Format("15:04"))
|
||||||
|
|
||||||
|
// Driver data
|
||||||
|
for _, field := range driverFields {
|
||||||
|
row = append(row, getAccountFieldValue(driver.Data, field))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beneficiary data
|
||||||
|
for _, field := range beneficiaryFields {
|
||||||
|
row = append(row, getAccountFieldValue(beneficiary.Data, field))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journey information
|
||||||
|
if booking.PassengerPickupAddress != nil {
|
||||||
|
row = append(row, *booking.PassengerPickupAddress)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if booking.PassengerDropAddress != nil {
|
||||||
|
row = append(row, *booking.PassengerDropAddress)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance
|
||||||
|
if booking.Distance != nil {
|
||||||
|
row = append(row, *booking.Distance)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration
|
||||||
|
if booking.Duration != nil {
|
||||||
|
row = append(row, *booking.Duration)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pricing
|
||||||
|
if booking.Price != nil && booking.Price.Amount != nil {
|
||||||
|
row = append(row, fmt.Sprintf("%.2f", *booking.Price.Amount))
|
||||||
|
if booking.Price.Currency != nil {
|
||||||
|
row = append(row, *booking.Price.Currency)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row = append(row, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver compensation
|
||||||
|
if booking.DriverCompensationAmount != nil {
|
||||||
|
row = append(row, fmt.Sprintf("%.2f", *booking.DriverCompensationAmount))
|
||||||
|
if booking.DriverCompensationCurrency != nil {
|
||||||
|
row = append(row, *booking.DriverCompensationCurrency)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row = append(row, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
spreadsheet.AddRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Excel to response
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"export-covoiturage-solidaire.xlsx\"")
|
||||||
|
|
||||||
|
if err := spreadsheet.GetFile().Write(w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error generating Excel file")
|
||||||
|
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *XLSXRenderer) OrganizedCarpoolDrivers(w http.ResponseWriter, result *application.OrganizedCarpoolOverviewResult) {
|
||||||
|
// Create Excel spreadsheet
|
||||||
|
spreadsheet := r.NewSpreadsheet("Covoitureurs solidaires")
|
||||||
|
|
||||||
|
// Build headers dynamically based on configuration
|
||||||
|
driverOptionalFields := r.Config.Get("modules.organized_carpool.drivers.profile_optional_fields")
|
||||||
|
driverFields := []string{"last_name", "first_name", "email", "phone_number", "birthdate", "gender", "file_number"}
|
||||||
|
headers := []string{"ID", "Nom", "Prénom", "Email", "Téléphone", "Date de naissance", "Genre", "Numéro de dossier"}
|
||||||
|
|
||||||
|
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range driverOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
driverFields = append(driverFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
headers = append(headers, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add address columns
|
||||||
|
headers = append(headers, "Adresse départ", "Adresse destination", "Archivé")
|
||||||
|
|
||||||
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for _, driver := range result.Accounts {
|
||||||
|
row := []interface{}{}
|
||||||
|
|
||||||
|
// Driver ID
|
||||||
|
row = append(row, driver.ID)
|
||||||
|
|
||||||
|
// Driver data
|
||||||
|
for _, field := range driverFields {
|
||||||
|
value := getAccountFieldValue(driver.Data, field)
|
||||||
|
// Convert gender code to text
|
||||||
|
if field == "gender" && value != "" {
|
||||||
|
value = gender.ISO5218ToString(value)
|
||||||
|
}
|
||||||
|
row = append(row, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address departure
|
||||||
|
addressDeparture := ""
|
||||||
|
if addr, ok := driver.Data["address"]; ok {
|
||||||
|
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||||
|
if props, ok := addrMap["properties"]; ok {
|
||||||
|
if propsMap, ok := props.(map[string]interface{}); ok {
|
||||||
|
if label, ok := propsMap["label"].(string); ok {
|
||||||
|
addressDeparture = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, addressDeparture)
|
||||||
|
|
||||||
|
// Address destination
|
||||||
|
addressDestination := ""
|
||||||
|
if addr, ok := driver.Data["address_destination"]; ok {
|
||||||
|
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||||
|
if props, ok := addrMap["properties"]; ok {
|
||||||
|
if propsMap, ok := props.(map[string]interface{}); ok {
|
||||||
|
if label, ok := propsMap["label"].(string); ok {
|
||||||
|
addressDestination = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, addressDestination)
|
||||||
|
|
||||||
|
// Archived status
|
||||||
|
archived := "Non"
|
||||||
|
if archivedVal, ok := driver.Data["archived"].(bool); ok && archivedVal {
|
||||||
|
archived = "Oui"
|
||||||
|
}
|
||||||
|
row = append(row, archived)
|
||||||
|
|
||||||
|
spreadsheet.AddRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Excel to response
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"export-covoitureurs-solidaires.xlsx\"")
|
||||||
|
|
||||||
|
if err := spreadsheet.GetFile().Write(w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error generating Excel file")
|
||||||
|
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,320 @@
|
||||||
|
package xlsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/gender"
|
||||||
|
"github.com/paulmach/orb/geojson"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *XLSXRenderer) SolidarityTransportBookings(w http.ResponseWriter, result *application.SolidarityTransportBookingsResult) {
|
||||||
|
// Create Excel spreadsheet
|
||||||
|
spreadsheet := r.NewSpreadsheet("Transport solidaire")
|
||||||
|
|
||||||
|
// Build headers dynamically based on configuration
|
||||||
|
headers := []string{
|
||||||
|
"ID Réservation",
|
||||||
|
"Statut",
|
||||||
|
"Motif de réservation",
|
||||||
|
"Raison d'annulation",
|
||||||
|
"Date de prise en charge",
|
||||||
|
"Heure de prise en charge",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add driver fields from config
|
||||||
|
driverOptionalFields := r.Config.Get("modules.solidarity_transport.profile_optional_fields")
|
||||||
|
driverFields := []string{"last_name", "first_name", "email", "phone_number"}
|
||||||
|
driverHeaders := []string{"Conducteur - Nom", "Conducteur - Prénom", "Conducteur - Email", "Conducteur - Téléphone"}
|
||||||
|
|
||||||
|
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range driverOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
driverFields = append(driverFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
driverHeaders = append(driverHeaders, fmt.Sprintf("Conducteur - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, driverHeaders...)
|
||||||
|
|
||||||
|
// Add beneficiary fields from config
|
||||||
|
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
|
||||||
|
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
|
||||||
|
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
|
||||||
|
|
||||||
|
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range beneficiaryOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
beneficiaryFields = append(beneficiaryFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, beneficiaryHeaders...)
|
||||||
|
|
||||||
|
// Add journey information headers
|
||||||
|
headers = append(headers,
|
||||||
|
"Lieu de départ - Adresse",
|
||||||
|
"Lieu de départ - Latitude",
|
||||||
|
"Lieu de départ - Longitude",
|
||||||
|
"Destination - Adresse",
|
||||||
|
"Destination - Latitude",
|
||||||
|
"Destination - Longitude",
|
||||||
|
"Distance passager (km)",
|
||||||
|
"Distance conducteur totale (km)",
|
||||||
|
"Durée trajet (minutes)",
|
||||||
|
"Prix passager",
|
||||||
|
"Devise prix passager",
|
||||||
|
"Compensation conducteur",
|
||||||
|
"Devise compensation",
|
||||||
|
"Temps d'attente retour",
|
||||||
|
"Aller simple",
|
||||||
|
"Départ conducteur - Adresse",
|
||||||
|
"Départ conducteur - Latitude",
|
||||||
|
"Départ conducteur - Longitude",
|
||||||
|
"Arrivée conducteur - Adresse",
|
||||||
|
"Arrivée conducteur - Latitude",
|
||||||
|
"Arrivée conducteur - Longitude",
|
||||||
|
)
|
||||||
|
|
||||||
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for _, booking := range result.Bookings {
|
||||||
|
driver := result.DriversMap[booking.DriverId]
|
||||||
|
beneficiary := result.BeneficiariesMap[booking.PassengerId]
|
||||||
|
|
||||||
|
row := []interface{}{}
|
||||||
|
|
||||||
|
// Booking information
|
||||||
|
row = append(row, booking.Id)
|
||||||
|
row = append(row, booking.Status)
|
||||||
|
|
||||||
|
// Motivation (from booking.Data)
|
||||||
|
motivation := ""
|
||||||
|
if booking.Data != nil {
|
||||||
|
if motivationVal, ok := booking.Data["motivation"]; ok && motivationVal != nil {
|
||||||
|
motivation = fmt.Sprint(motivationVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, motivation)
|
||||||
|
|
||||||
|
// Cancellation reason (from booking.Data)
|
||||||
|
cancellationReason := ""
|
||||||
|
if booking.Data != nil {
|
||||||
|
if reasonVal, ok := booking.Data["reason"]; ok && reasonVal != nil {
|
||||||
|
cancellationReason = fmt.Sprint(reasonVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, cancellationReason)
|
||||||
|
|
||||||
|
// Journey date and time
|
||||||
|
if booking.Journey != nil {
|
||||||
|
row = append(row, booking.Journey.PassengerPickupDate.Format("2006-01-02"))
|
||||||
|
row = append(row, booking.Journey.PassengerPickupDate.Format("15:04"))
|
||||||
|
} else {
|
||||||
|
row = append(row, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver data
|
||||||
|
for _, field := range driverFields {
|
||||||
|
row = append(row, getAccountFieldValue(driver.Data, field))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beneficiary data
|
||||||
|
for _, field := range beneficiaryFields {
|
||||||
|
row = append(row, getAccountFieldValue(beneficiary.Data, field))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journey locations and details
|
||||||
|
if booking.Journey != nil {
|
||||||
|
// Passenger pickup
|
||||||
|
pickupAddr, pickupLat, pickupLon := getLocationData(booking.Journey.PassengerPickup)
|
||||||
|
row = append(row, pickupAddr, pickupLat, pickupLon)
|
||||||
|
|
||||||
|
// Passenger drop
|
||||||
|
dropAddr, dropLat, dropLon := getLocationData(booking.Journey.PassengerDrop)
|
||||||
|
row = append(row, dropAddr, dropLat, dropLon)
|
||||||
|
|
||||||
|
// Distances and duration
|
||||||
|
row = append(row, booking.Journey.PassengerDistance)
|
||||||
|
row = append(row, booking.Journey.DriverDistance)
|
||||||
|
row = append(row, int64(booking.Journey.Duration.Minutes()))
|
||||||
|
|
||||||
|
// Pricing
|
||||||
|
row = append(row, fmt.Sprintf("%.2f", booking.Journey.Price.Amount))
|
||||||
|
row = append(row, booking.Journey.Price.Currency)
|
||||||
|
|
||||||
|
// Driver compensation
|
||||||
|
if booking.DriverCompensationAmount > 0 {
|
||||||
|
row = append(row, fmt.Sprintf("%.2f", booking.DriverCompensationAmount))
|
||||||
|
row = append(row, booking.DriverCompensationCurrency)
|
||||||
|
} else {
|
||||||
|
row = append(row, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return wait time
|
||||||
|
row = append(row, "")
|
||||||
|
|
||||||
|
// One way trip (Noreturn field)
|
||||||
|
if booking.Journey.Noreturn {
|
||||||
|
row = append(row, "Oui")
|
||||||
|
} else {
|
||||||
|
row = append(row, "Non")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver departure
|
||||||
|
driverDepartAddr, driverDepartLat, driverDepartLon := getLocationData(booking.Journey.DriverDeparture)
|
||||||
|
row = append(row, driverDepartAddr, driverDepartLat, driverDepartLon)
|
||||||
|
|
||||||
|
// Driver arrival
|
||||||
|
driverArrivalAddr, driverArrivalLat, driverArrivalLon := getLocationData(booking.Journey.DriverArrival)
|
||||||
|
row = append(row, driverArrivalAddr, driverArrivalLat, driverArrivalLon)
|
||||||
|
} else {
|
||||||
|
// No journey data - fill with empty values
|
||||||
|
for i := 0; i < 21; i++ {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spreadsheet.AddRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Excel to response
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"export-transport-solidaire.xlsx\"")
|
||||||
|
|
||||||
|
if err := spreadsheet.GetFile().Write(w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error generating Excel file")
|
||||||
|
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccountFieldValue(data map[string]interface{}, field string) string {
|
||||||
|
// First check direct field
|
||||||
|
if val, ok := data[field]; ok && val != nil {
|
||||||
|
return fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check in other_properties
|
||||||
|
if otherProps, ok := data["other_properties"]; ok {
|
||||||
|
if otherPropsMap, ok := otherProps.(map[string]interface{}); ok {
|
||||||
|
if val, ok := otherPropsMap[field]; ok && val != nil {
|
||||||
|
return fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocationData(feature *geojson.Feature) (address string, lat interface{}, lon interface{}) {
|
||||||
|
if feature != nil && feature.Properties != nil {
|
||||||
|
if label, ok := feature.Properties["label"].(string); ok {
|
||||||
|
address = label
|
||||||
|
}
|
||||||
|
if feature.Geometry != nil {
|
||||||
|
coords := feature.Geometry.Bound().Center()
|
||||||
|
lat = fmt.Sprintf("%.6f", coords.Lat())
|
||||||
|
lon = fmt.Sprintf("%.6f", coords.Lon())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *XLSXRenderer) SolidarityTransportDrivers(w http.ResponseWriter, result *application.SolidarityTransportOverviewResult) {
|
||||||
|
// Create Excel spreadsheet
|
||||||
|
spreadsheet := r.NewSpreadsheet("Conducteurs solidaires")
|
||||||
|
|
||||||
|
// Build headers dynamically based on configuration
|
||||||
|
driverOptionalFields := r.Config.Get("modules.solidarity_transport.drivers.profile_optional_fields")
|
||||||
|
driverFields := []string{"last_name", "first_name", "email", "phone_number", "birthdate", "gender", "file_number"}
|
||||||
|
headers := []string{"ID", "Nom", "Prénom", "Email", "Téléphone", "Date de naissance", "Genre", "Numéro de dossier"}
|
||||||
|
|
||||||
|
if driverOptionalFieldsList, ok := driverOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range driverOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
driverFields = append(driverFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
headers = append(headers, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add address and archived columns
|
||||||
|
headers = append(headers, "Adresse", "Archivé")
|
||||||
|
|
||||||
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for _, driver := range result.Accounts {
|
||||||
|
row := []interface{}{}
|
||||||
|
|
||||||
|
// Driver ID
|
||||||
|
row = append(row, driver.ID)
|
||||||
|
|
||||||
|
// Driver data
|
||||||
|
for _, field := range driverFields {
|
||||||
|
value := getAccountFieldValue(driver.Data, field)
|
||||||
|
// Convert gender code to text
|
||||||
|
if field == "gender" && value != "" {
|
||||||
|
value = gender.ISO5218ToString(value)
|
||||||
|
}
|
||||||
|
row = append(row, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address
|
||||||
|
address := ""
|
||||||
|
if addr, ok := driver.Data["address"]; ok {
|
||||||
|
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||||
|
if props, ok := addrMap["properties"]; ok {
|
||||||
|
if propsMap, ok := props.(map[string]interface{}); ok {
|
||||||
|
if label, ok := propsMap["label"].(string); ok {
|
||||||
|
address = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, address)
|
||||||
|
|
||||||
|
// Archived status
|
||||||
|
archived := "Non"
|
||||||
|
if archivedVal, ok := driver.Data["archived"].(bool); ok && archivedVal {
|
||||||
|
archived = "Oui"
|
||||||
|
}
|
||||||
|
row = append(row, archived)
|
||||||
|
|
||||||
|
spreadsheet.AddRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Excel to response
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"export-conducteurs-solidaires.xlsx\"")
|
||||||
|
|
||||||
|
if err := spreadsheet.GetFile().Write(w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error generating Excel file")
|
||||||
|
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,350 @@
|
||||||
|
package xlsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||||
|
groupsstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||||
|
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *XLSXRenderer) VehicleBookings(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]fleetsstorage.Vehicle, driversMap map[string]mobilityaccountsstorage.Account) {
|
||||||
|
// Create Excel spreadsheet
|
||||||
|
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
|
||||||
|
|
||||||
|
// Build headers
|
||||||
|
headers := []string{
|
||||||
|
"ID Réservation",
|
||||||
|
"Statut",
|
||||||
|
"Type de véhicule",
|
||||||
|
"Nom du véhicule",
|
||||||
|
"Immatriculation",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add beneficiary fields from config
|
||||||
|
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
|
||||||
|
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
|
||||||
|
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
|
||||||
|
|
||||||
|
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range beneficiaryOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
beneficiaryFields = append(beneficiaryFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, beneficiaryHeaders...)
|
||||||
|
|
||||||
|
// Add booking date headers
|
||||||
|
headers = append(headers,
|
||||||
|
"Date de début",
|
||||||
|
"Date de fin",
|
||||||
|
"Durée (jours)",
|
||||||
|
"Commentaire",
|
||||||
|
"Raison d'annulation",
|
||||||
|
)
|
||||||
|
|
||||||
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for _, booking := range bookings {
|
||||||
|
vehicle := vehiclesMap[booking.Vehicleid]
|
||||||
|
beneficiary := driversMap[booking.Driver]
|
||||||
|
|
||||||
|
row := []interface{}{}
|
||||||
|
|
||||||
|
// Booking information
|
||||||
|
row = append(row, booking.ID)
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status := ""
|
||||||
|
if booking.Deleted {
|
||||||
|
status = "Annulé"
|
||||||
|
} else {
|
||||||
|
switch booking.Status() {
|
||||||
|
case 1:
|
||||||
|
status = "A venir"
|
||||||
|
case 0:
|
||||||
|
status = "En cours"
|
||||||
|
case -1:
|
||||||
|
status = "Terminé"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, status)
|
||||||
|
|
||||||
|
// Vehicle information
|
||||||
|
row = append(row, vehicle.Type)
|
||||||
|
if vehicleName, ok := vehicle.Data["name"].(string); ok {
|
||||||
|
row = append(row, vehicleName)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
if licencePlate, ok := vehicle.Data["licence_plate"].(string); ok {
|
||||||
|
row = append(row, licencePlate)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beneficiary data (including other_properties)
|
||||||
|
for _, field := range beneficiaryFields {
|
||||||
|
value := ""
|
||||||
|
// First check direct field
|
||||||
|
if val, ok := beneficiary.Data[field]; ok && val != nil {
|
||||||
|
value = fmt.Sprint(val)
|
||||||
|
} else {
|
||||||
|
// Check in other_properties
|
||||||
|
if otherProps, ok := beneficiary.Data["other_properties"]; ok {
|
||||||
|
if otherPropsMap, ok := otherProps.(map[string]interface{}); ok {
|
||||||
|
if val, ok := otherPropsMap[field]; ok && val != nil {
|
||||||
|
value = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Booking dates
|
||||||
|
row = append(row, booking.Startdate.Format("2006-01-02"))
|
||||||
|
row = append(row, booking.Enddate.Format("2006-01-02"))
|
||||||
|
|
||||||
|
// Duration in days
|
||||||
|
duration := booking.Enddate.Sub(booking.Startdate).Hours() / 24
|
||||||
|
row = append(row, fmt.Sprintf("%.0f", duration))
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
comment := ""
|
||||||
|
if booking.Data != nil {
|
||||||
|
if commentVal, ok := booking.Data["comment"]; ok && commentVal != nil {
|
||||||
|
comment = fmt.Sprint(commentVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, comment)
|
||||||
|
|
||||||
|
// Cancellation reason
|
||||||
|
cancellationReason := ""
|
||||||
|
if booking.Deleted && booking.Data != nil {
|
||||||
|
if reasonVal, ok := booking.Data["reason"]; ok && reasonVal != nil {
|
||||||
|
cancellationReason = fmt.Sprint(reasonVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, cancellationReason)
|
||||||
|
|
||||||
|
spreadsheet.AddRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Excel to response
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"export-reservations-vehicules.xlsx\"")
|
||||||
|
|
||||||
|
if err := spreadsheet.GetFile().Write(w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error generating Excel file")
|
||||||
|
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *XLSXRenderer) VehicleBookingsAdmin(w http.ResponseWriter, bookings []fleetsstorage.Booking, vehiclesMap map[string]interface{}, driversMap map[string]interface{}, groupsMap map[string]any) {
|
||||||
|
// Create Excel spreadsheet
|
||||||
|
spreadsheet := r.NewSpreadsheet("Réservations véhicules")
|
||||||
|
|
||||||
|
// Build headers
|
||||||
|
headers := []string{
|
||||||
|
"ID Réservation",
|
||||||
|
"Statut",
|
||||||
|
"Date de début",
|
||||||
|
"Date de fin",
|
||||||
|
"Durée (jours)",
|
||||||
|
"Commentaire",
|
||||||
|
"Raison d'annulation",
|
||||||
|
"Type de véhicule",
|
||||||
|
"Nom du véhicule",
|
||||||
|
"Immatriculation",
|
||||||
|
"Gestionnaire véhicule",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add vehicle optional fields from config
|
||||||
|
vehicleOptionalFields := r.Config.Get("modules.fleets.vehicle_optional_fields")
|
||||||
|
vehicleFields := []string{}
|
||||||
|
vehicleHeaders := []string{}
|
||||||
|
|
||||||
|
if vehicleOptionalFieldsList, ok := vehicleOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range vehicleOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
vehicleFields = append(vehicleFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
vehicleHeaders = append(vehicleHeaders, fmt.Sprintf("Véhicule - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, vehicleHeaders...)
|
||||||
|
|
||||||
|
// Add beneficiary fields from config
|
||||||
|
beneficiaryOptionalFields := r.Config.Get("modules.beneficiaries.profile_optional_fields")
|
||||||
|
beneficiaryFields := []string{"last_name", "first_name", "email", "phone_number"}
|
||||||
|
beneficiaryHeaders := []string{"Bénéficiaire - Nom", "Bénéficiaire - Prénom", "Bénéficiaire - Email", "Bénéficiaire - Téléphone"}
|
||||||
|
|
||||||
|
if beneficiaryOptionalFieldsList, ok := beneficiaryOptionalFields.([]interface{}); ok {
|
||||||
|
for _, field := range beneficiaryOptionalFieldsList {
|
||||||
|
if fieldMap, ok := field.(map[string]interface{}); ok {
|
||||||
|
if name, ok := fieldMap["name"].(string); ok {
|
||||||
|
beneficiaryFields = append(beneficiaryFields, name)
|
||||||
|
label := name
|
||||||
|
if labelVal, ok := fieldMap["label"].(string); ok {
|
||||||
|
label = labelVal
|
||||||
|
}
|
||||||
|
beneficiaryHeaders = append(beneficiaryHeaders, fmt.Sprintf("Bénéficiaire - %s", label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = append(headers, beneficiaryHeaders...)
|
||||||
|
|
||||||
|
spreadsheet.SetHeaders(headers)
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for _, booking := range bookings {
|
||||||
|
// Get vehicle from map
|
||||||
|
var vehicle fleetsstorage.Vehicle
|
||||||
|
if v, ok := vehiclesMap[booking.Vehicleid]; ok {
|
||||||
|
if vTyped, ok := v.(fleetsstorage.Vehicle); ok {
|
||||||
|
vehicle = vTyped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get beneficiary from map
|
||||||
|
var beneficiary mobilityaccountsstorage.Account
|
||||||
|
if d, ok := driversMap[booking.Driver]; ok {
|
||||||
|
if dTyped, ok := d.(mobilityaccountsstorage.Account); ok {
|
||||||
|
beneficiary = dTyped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row := []interface{}{}
|
||||||
|
|
||||||
|
// Booking information
|
||||||
|
row = append(row, booking.ID)
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status := ""
|
||||||
|
if booking.Deleted {
|
||||||
|
status = "Annulé"
|
||||||
|
} else {
|
||||||
|
switch booking.Status() {
|
||||||
|
case 1:
|
||||||
|
status = "A venir"
|
||||||
|
case 0:
|
||||||
|
status = "En cours"
|
||||||
|
case -1:
|
||||||
|
status = "Terminé"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, status)
|
||||||
|
|
||||||
|
// Booking dates
|
||||||
|
row = append(row, booking.Startdate.Format("2006-01-02"))
|
||||||
|
row = append(row, booking.Enddate.Format("2006-01-02"))
|
||||||
|
|
||||||
|
// Duration in days
|
||||||
|
duration := booking.Enddate.Sub(booking.Startdate).Hours() / 24
|
||||||
|
row = append(row, fmt.Sprintf("%.0f", duration))
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
comment := ""
|
||||||
|
if booking.Data != nil {
|
||||||
|
if commentVal, ok := booking.Data["comment"]; ok && commentVal != nil {
|
||||||
|
comment = fmt.Sprint(commentVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, comment)
|
||||||
|
|
||||||
|
// Cancellation reason
|
||||||
|
cancellationReason := ""
|
||||||
|
if booking.Deleted && booking.Data != nil {
|
||||||
|
if reasonVal, ok := booking.Data["reason"]; ok && reasonVal != nil {
|
||||||
|
cancellationReason = fmt.Sprint(reasonVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, cancellationReason)
|
||||||
|
|
||||||
|
// Vehicle information
|
||||||
|
row = append(row, vehicle.Type)
|
||||||
|
if vehicleName, ok := vehicle.Data["name"].(string); ok {
|
||||||
|
row = append(row, vehicleName)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
if licencePlate, ok := vehicle.Data["licence_plate"].(string); ok {
|
||||||
|
row = append(row, licencePlate)
|
||||||
|
} else {
|
||||||
|
row = append(row, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vehicle administrator (group name)
|
||||||
|
administratorName := ""
|
||||||
|
if len(vehicle.Administrators) > 0 {
|
||||||
|
if group, ok := groupsMap[vehicle.Administrators[0]]; ok {
|
||||||
|
if groupTyped, ok := group.(groupsstorage.Group); ok {
|
||||||
|
if name, ok := groupTyped.Data["name"]; ok {
|
||||||
|
administratorName = fmt.Sprint(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, administratorName)
|
||||||
|
|
||||||
|
// Vehicle optional fields
|
||||||
|
for _, field := range vehicleFields {
|
||||||
|
value := ""
|
||||||
|
if val, ok := vehicle.Data[field]; ok && val != nil {
|
||||||
|
value = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
row = append(row, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beneficiary data (including other_properties)
|
||||||
|
for _, field := range beneficiaryFields {
|
||||||
|
value := ""
|
||||||
|
// First check direct field
|
||||||
|
if val, ok := beneficiary.Data[field]; ok && val != nil {
|
||||||
|
value = fmt.Sprint(val)
|
||||||
|
} else {
|
||||||
|
// Check in other_properties
|
||||||
|
if otherProps, ok := beneficiary.Data["other_properties"]; ok {
|
||||||
|
if otherPropsMap, ok := otherProps.(map[string]interface{}); ok {
|
||||||
|
if val, ok := otherPropsMap[field]; ok && val != nil {
|
||||||
|
value = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = append(row, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
spreadsheet.AddRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Excel to response
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"all_bookings.xlsx\"")
|
||||||
|
|
||||||
|
if err := spreadsheet.GetFile().Write(w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error generating Excel file")
|
||||||
|
http.Error(w, "Error generating Excel file", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package xlsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XLSXRenderer handles rendering data to Excel XLSX format
|
||||||
|
type XLSXRenderer struct {
|
||||||
|
Config *viper.Viper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXLSXRenderer(config *viper.Viper) *XLSXRenderer {
|
||||||
|
return &XLSXRenderer{
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spreadsheet represents an Excel spreadsheet
|
||||||
|
type Spreadsheet struct {
|
||||||
|
file *excelize.File
|
||||||
|
sheetName string
|
||||||
|
rowIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSpreadsheet creates a new Excel spreadsheet
|
||||||
|
func (r *XLSXRenderer) NewSpreadsheet(sheetName string) *Spreadsheet {
|
||||||
|
f := excelize.NewFile()
|
||||||
|
// Rename default sheet
|
||||||
|
f.SetSheetName("Sheet1", sheetName)
|
||||||
|
|
||||||
|
return &Spreadsheet{
|
||||||
|
file: f,
|
||||||
|
sheetName: sheetName,
|
||||||
|
rowIndex: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHeaders sets the header row
|
||||||
|
func (s *Spreadsheet) SetHeaders(headers []string) {
|
||||||
|
for i, header := range headers {
|
||||||
|
cell, _ := excelize.CoordinatesToCellName(i+1, s.rowIndex)
|
||||||
|
s.file.SetCellValue(s.sheetName, cell, header)
|
||||||
|
}
|
||||||
|
s.rowIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRow adds a data row
|
||||||
|
func (s *Spreadsheet) AddRow(values []interface{}) {
|
||||||
|
for i, value := range values {
|
||||||
|
cell, _ := excelize.CoordinatesToCellName(i+1, s.rowIndex)
|
||||||
|
s.file.SetCellValue(s.sheetName, cell, value)
|
||||||
|
}
|
||||||
|
s.rowIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFile returns the underlying excelize File
|
||||||
|
func (s *Spreadsheet) GetFile() *excelize.File {
|
||||||
|
return s.file
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
if code == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, _ := h.idp.SessionsStore.Get(r, "parcoursmob_session")
|
||||||
|
redirectSession := ""
|
||||||
|
if session.Values["redirect"] != nil && session.Values["redirect"] != "" {
|
||||||
|
redirectSession = session.Values["redirect"].(string)
|
||||||
|
delete(session.Values, "redirect")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.applicationHandler.ProcessOAuth2Callback(code, redirectSession)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Values["idtoken"] = result.IDToken
|
||||||
|
|
||||||
|
if err = session.Save(r, w); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Cannot save session")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, result.RedirectURL, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/cache"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) GetCache(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
cacheID := vars["cacheid"]
|
||||||
|
|
||||||
|
// Parse query parameters
|
||||||
|
limitsMinStr := r.URL.Query().Get("limits.min")
|
||||||
|
limitsMaxStr := r.URL.Query().Get("limits.max")
|
||||||
|
limitsMin, limitsMax := cache.ParseLimits(limitsMinStr, limitsMaxStr)
|
||||||
|
|
||||||
|
// Use a channel to synchronize the goroutines
|
||||||
|
ch := make(chan []byte)
|
||||||
|
|
||||||
|
// Fetch data from cache asynchronously
|
||||||
|
go func() {
|
||||||
|
result, err := h.cacheService.GetCacheData(cacheID, limitsMin, limitsMax)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to get cache data")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
ch <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- result.Data // Signal that the data has been fetched successfully
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for the JSON marshaling goroutine to finish
|
||||||
|
data := <-ch
|
||||||
|
if data == nil {
|
||||||
|
return // Stop processing if an error occurred
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the JSON response to the client
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(data)
|
||||||
|
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) CalendarGlobal(w http.ResponseWriter, r *http.Request) {
|
||||||
|
enabled := h.config.GetBool("modules.agenda.enabled") && h.config.GetBool("modules.agenda.calendars.global.enabled")
|
||||||
|
if !enabled {
|
||||||
|
log.Error().Msg("global calendar not activated in configuration")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.applicationHandler.GenerateGlobalCalendar(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error generating global calendar")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/calendar; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(result.CalendarData))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) CalendarOrganizations(w http.ResponseWriter, r *http.Request) {
|
||||||
|
enabled := h.config.GetBool("modules.agenda.enabled") && h.config.GetBool("modules.agenda.calendars.organizations.enabled")
|
||||||
|
if !enabled {
|
||||||
|
log.Error().Msg("organizations calendar not activated in configuration")
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
groupID := vars["groupid"]
|
||||||
|
|
||||||
|
result, err := h.applicationHandler.GenerateOrganizationCalendar(r.Context(), groupID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error generating organization calendar")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/calendar; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(result.CalendarData))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) CacheExport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
cacheID := vars["cacheid"]
|
||||||
|
|
||||||
|
result, err := h.applicationHandler.ExportCacheAsCSV(cacheID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error exporting cache")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/csv")
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=export-%s.csv", cacheID))
|
||||||
|
|
||||||
|
csvWriter := csv.NewWriter(w)
|
||||||
|
defer csvWriter.Flush()
|
||||||
|
|
||||||
|
csvWriter.Write(result.Headers)
|
||||||
|
csvWriter.WriteAll(result.Values)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) GeoAutocomplete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
t, ok := r.URL.Query()["text"]
|
||||||
|
if !ok || len(t[0]) < 1 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text := t[0]
|
||||||
|
|
||||||
|
result, err := h.geoService.Autocomplete(text)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.Marshal(result.Features)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(j)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/cache"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/geo"
|
||||||
|
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
|
||||||
|
cacheStorage "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
config *viper.Viper
|
||||||
|
idp *identification.IdentificationProvider
|
||||||
|
applicationHandler *application.ApplicationHandler
|
||||||
|
cacheService *cache.CacheService
|
||||||
|
geoService *geo.GeoService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, appHandler *application.ApplicationHandler, cacheHandler cacheStorage.CacheHandler) *Handler {
|
||||||
|
cacheService := cache.NewCacheService(cacheHandler)
|
||||||
|
geoService := geo.NewGeoService(cfg.GetString("geo.pelias.url"))
|
||||||
|
|
||||||
|
return &Handler{
|
||||||
|
config: cfg,
|
||||||
|
idp: idp,
|
||||||
|
applicationHandler: appHandler,
|
||||||
|
cacheService: cacheService,
|
||||||
|
geoService: geoService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) NotFound(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue