eintopf.info@v0.13.16/service/event/event.go (about)

     1  // Copyright (C) 2022 The Eintopf authors
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  
    16  package event
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"strings"
    22  	"time"
    23  
    24  	"eintopf.info/internal/crud"
    25  	"eintopf.info/service/auth"
    26  )
    27  
    28  // NewEvent defines the data for a new event.
    29  type NewEvent struct {
    30  	Published    bool       `json:"published"`
    31  	Parent       string     `json:"parent"`
    32  	ParentListed bool       `json:"parentListed"`
    33  	Name         string     `json:"name"`
    34  	Organizers   []string   `json:"organizers"`
    35  	Involved     []Involved `json:"involved"`
    36  	Location     string     `json:"location"`
    37  	Location2    string     `json:"location2"`
    38  	Description  string     `json:"description"`
    39  	Start        time.Time  `json:"start"`
    40  	End          *time.Time `json:"end"`
    41  	Tags         []string   `json:"tags"`
    42  	Image        string     `json:"image"`
    43  	OwnedBy      []string   `json:"ownedBy"`
    44  }
    45  
    46  // Involved defines an entity, that is involved with an event.
    47  type Involved struct {
    48  	Name        string `json:"name" db:"name"`
    49  	Description string `json:"description" db:"description"`
    50  }
    51  
    52  // IsOwned returns true if the id is in the OwnedBy field.
    53  func (e *NewEvent) IsOwned(id string) bool {
    54  	owned := false
    55  	for _, owner := range e.OwnedBy {
    56  		if owner == id {
    57  			owned = true
    58  		}
    59  	}
    60  	return owned
    61  }
    62  
    63  // Event defines a calendar event.
    64  // It implements indexo.Coppyable.
    65  type Event struct {
    66  	ID           string     `json:"id" db:"id"`
    67  	Deactivated  bool       `json:"deactivated" db:"deactivated"`
    68  	Published    bool       `json:"published" db:"published"`
    69  	Canceled     bool       `json:"canceled" db:"canceled"`
    70  	Parent       string     `json:"parent" db:"parent"`
    71  	ParentListed bool       `json:"parentListed" db:"parentListed"`
    72  	Name         string     `json:"name" db:"name"`
    73  	Organizers   []string   `json:"organizers" db:"organizers"`
    74  	Involved     []Involved `json:"involved" db:"involved"`
    75  	Location     string     `json:"location" db:"location"`
    76  	Location2    string     `json:"location2" db:"location2"`
    77  	Description  string     `json:"description" db:"description"`
    78  	Start        time.Time  `json:"start" db:"start"`
    79  	End          *time.Time `json:"end" db:"end"`
    80  	Tags         []string   `json:"tags" db:"tags"`
    81  	Image        string     `json:"image" db:"image"`
    82  	OwnedBy      []string   `json:"ownedBy" db:"ownedBy"`
    83  }
    84  
    85  func (e Event) Identifier() string {
    86  	return e.ID
    87  }
    88  
    89  func EventFromNewEvent(newEvent *NewEvent, id string) *Event {
    90  	return &Event{
    91  		ID:           id,
    92  		Deactivated:  false,
    93  		Published:    newEvent.Published,
    94  		Parent:       newEvent.Parent,
    95  		ParentListed: newEvent.ParentListed,
    96  		Name:         newEvent.Name,
    97  		Organizers:   newEvent.Organizers,
    98  		Involved:     newEvent.Involved,
    99  		Location:     newEvent.Location,
   100  		Location2:    newEvent.Location2,
   101  		Description:  newEvent.Description,
   102  		Image:        newEvent.Image,
   103  		Start:        newEvent.Start,
   104  		End:          newEvent.End,
   105  		Tags:         newEvent.Tags,
   106  		OwnedBy:      newEvent.OwnedBy,
   107  	}
   108  }
   109  
   110  // IsOwned returns true if the id is in the OwnedBy field.
   111  func (e *Event) IsOwned(id string) bool {
   112  	owned := false
   113  	for _, owner := range e.OwnedBy {
   114  		if owner == id {
   115  			owned = true
   116  		}
   117  	}
   118  	return owned
   119  }
   120  
   121  // Indexable indicates wether the event should be indexed in the search.
   122  // All events are indexable if they are not deactivated.
   123  func (e *Event) Indexable() bool {
   124  	if e.Deactivated {
   125  		return false
   126  	}
   127  	return true
   128  }
   129  
   130  // Listable indicates wether the event should be shown on list pages.
   131  func (e *Event) Listable() bool {
   132  	if !e.Published {
   133  		return false
   134  	}
   135  	if e.Parent != "" {
   136  		if strings.HasPrefix(e.Parent, "revent") {
   137  			return true
   138  		}
   139  		if !e.ParentListed {
   140  			return false
   141  		}
   142  	}
   143  	return true
   144  }
   145  
   146  // FindFilters defines the possible filters for the find method.
   147  type FindFilters struct {
   148  	ID                  *string    `json:"id,omitempty"`
   149  	Deactivated         *bool      `json:"deactivated,omitempty"`
   150  	Published           *bool      `json:"published,omitempty"`
   151  	Canceled            *bool      `json:"canceled,omitempty"`
   152  	Parent              *string    `json:"parent,omitempty"`
   153  	Name                *string    `json:"name,omitempty"`
   154  	LikeName            *string    `json:"likeName,omitempty"`
   155  	Organizers          []string   `json:"organizers,omitempty"`
   156  	Location            *string    `json:"location,omitempty"`
   157  	Location2           *string    `json:"location2,omitempty"`
   158  	Description         *string    `json:"description,omitempty"`
   159  	Start               *time.Time `json:"start,omitempty"`
   160  	StartBefore         *time.Time `json:"startBefore,omitempty"`
   161  	StartAfter          *time.Time `json:"startAfter,omitempty"`
   162  	End                 *time.Time `json:"end,omitempty"`
   163  	EndBefore           *time.Time `json:"endBefore,omitempty"`
   164  	EndAfter            *time.Time `json:"endAfter,omitempty"`
   165  	Tags                []string   `json:"tags,omitempty"`
   166  	OwnedBy             []string   `json:"ownedBy,omitempty"`
   167  	OwnedByOrOrganizers []string
   168  }
   169  
   170  // Storer defines a service for CRUD operations on the event model.
   171  type Storer = crud.Storer[NewEvent, Event, FindFilters]
   172  
   173  // NewService returns a new event service.
   174  func NewService(store Storer, groupOwnerService groupOwnerService) *Service {
   175  	return &Service{store: store, groupOwnerService: groupOwnerService}
   176  }
   177  
   178  type Service struct {
   179  	store             Storer
   180  	groupOwnerService groupOwnerService
   181  }
   182  
   183  type groupOwnerService interface {
   184  	// GroupIDsByOwners returns all group id for any of the owners.
   185  	GroupIDsByOwners(ctx context.Context, owners []string) ([]string, error)
   186  }
   187  
   188  func (s *Service) Create(ctx context.Context, newEvent *NewEvent) (*Event, error) {
   189  	id, err := auth.UserIDFromContext(ctx)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	if !newEvent.IsOwned(id) {
   194  		newEvent.OwnedBy = append(newEvent.OwnedBy, id)
   195  	}
   196  	newEvent.Name = strings.TrimSpace(newEvent.Name)
   197  	return s.store.Create(ctx, newEvent)
   198  }
   199  
   200  func (s *Service) Update(ctx context.Context, event *Event) (*Event, error) {
   201  	return s.store.Update(ctx, event)
   202  }
   203  
   204  func (s *Service) Delete(ctx context.Context, id string) error {
   205  	return s.store.Delete(ctx, id)
   206  }
   207  
   208  func (s *Service) FindByID(ctx context.Context, id string) (*Event, error) {
   209  	return s.store.FindByID(ctx, id)
   210  }
   211  
   212  func (s *Service) Find(ctx context.Context, params *crud.FindParams[FindFilters]) ([]*Event, int, error) {
   213  	if params != nil && params.Filters != nil && params.Filters.OwnedBy != nil {
   214  		if len(params.Filters.OwnedBy) == 1 && params.Filters.OwnedBy[0] == "self" {
   215  			id, err := auth.UserIDFromContext(ctx)
   216  			if err == nil {
   217  				params.Filters.OwnedBy = []string{id}
   218  			}
   219  		}
   220  
   221  		ownedGroupIDs, err := s.groupOwnerService.GroupIDsByOwners(ctx, params.Filters.OwnedBy)
   222  		if err != nil {
   223  			return nil, 0, err
   224  		}
   225  		if len(ownedGroupIDs) > 0 {
   226  			for i := range ownedGroupIDs {
   227  				ownedGroupIDs[i] = fmt.Sprintf("id:%s", ownedGroupIDs[i])
   228  			}
   229  			params.Filters.OwnedByOrOrganizers = ownedGroupIDs
   230  		}
   231  	}
   232  
   233  	events, total, err := s.store.Find(ctx, params)
   234  	if err != nil {
   235  		return nil, 0, err
   236  	}
   237  	return events, total, nil
   238  }
   239  
   240  func containsOwnedBy(ownedBy, filter []string) bool {
   241  	for _, o := range ownedBy {
   242  		for _, f := range filter {
   243  			if o == f {
   244  				return true
   245  			}
   246  		}
   247  	}
   248  	return false
   249  }