603 lines
16 KiB
Go
Executable File
603 lines
16 KiB
Go
Executable File
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
|
|
}
|
|
|
|
|
|
|
|
|
|
|