eintopf.info@v0.13.16/service/rss/rss.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 rss
    17  
    18  import (
    19  	"context"
    20  	"encoding/xml"
    21  	"fmt"
    22  	"time"
    23  
    24  	"eintopf.info/service/event"
    25  	"eintopf.info/service/eventsearch"
    26  	"eintopf.info/service/group"
    27  	"eintopf.info/service/place"
    28  )
    29  
    30  type Service interface {
    31  	Feed(ctx context.Context, opts eventsearch.Options, channel *Channel) (*Feed, error)
    32  	GroupFeed(ctx context.Context, groupID string) (*Feed, error)
    33  	PlaceFeed(ctx context.Context, placeID string) (*Feed, error)
    34  }
    35  
    36  type Feed struct {
    37  	XMLName xml.Name `xml:"rss"`
    38  	Version string   `xml:"version,attr"`
    39  	Channel *Channel `xml:"channel"`
    40  }
    41  
    42  type Channel struct {
    43  	XMLName     xml.Name `xml:"channel"`
    44  	Title       string   `xml:"title"`
    45  	Link        string   `xml:"link"`
    46  	Description string   `xml:"description"`
    47  	Items       []Item   `xml:"item"`
    48  }
    49  
    50  type Item struct {
    51  	XMLName     xml.Name `xml:"item"`
    52  	Title       string   `xml:"title"`
    53  	Link        string   `xml:"link"`
    54  	Description string   `xml:"description"`
    55  	Guid        string   `xml:"guid,omitempty"`
    56  }
    57  
    58  type ServiceImpl struct {
    59  	eventsearchService eventsearch.Service
    60  	groupService       group.Service
    61  	placeService       place.Service
    62  
    63  	baseURL string
    64  }
    65  
    66  func NewService(eventsearchService eventsearch.Service, groupService group.Service, placeService place.Service, baseURL string) *ServiceImpl {
    67  	return &ServiceImpl{
    68  		eventsearchService: eventsearchService,
    69  		groupService:       groupService,
    70  		placeService:       placeService,
    71  
    72  		baseURL: baseURL,
    73  	}
    74  }
    75  
    76  func (s *ServiceImpl) Feed(ctx context.Context, opts eventsearch.Options, channel *Channel) (*Feed, error) {
    77  	if opts.Sort == "" {
    78  		opts.Sort = "start"
    79  	}
    80  	if opts.Filters == nil {
    81  		opts.Filters = []eventsearch.Filter{eventsearch.ListedFilter{Listed: true}}
    82  	} else {
    83  		foundListedFilter := false
    84  		for _, f := range opts.Filters {
    85  			if _, ok := f.(eventsearch.ListedFilter); ok {
    86  				foundListedFilter = true
    87  				break
    88  			}
    89  		}
    90  		if !foundListedFilter {
    91  			opts.Filters = append(opts.Filters, eventsearch.ListedFilter{Listed: true})
    92  		}
    93  	}
    94  	opts.Filters = append(opts.Filters, eventsearch.DateRangeFilter{DateMin: time.Now()})
    95  	result, err := s.eventsearchService.Search(ctx, opts)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return s.feedFromEvents(result.Events, channel)
   100  }
   101  
   102  func (s *ServiceImpl) feedFromEvents(events []*eventsearch.EventDocument, channel *Channel) (*Feed, error) {
   103  	feed := &Feed{
   104  		Version: "2.0",
   105  		Channel: channel,
   106  	}
   107  
   108  	for _, e := range events {
   109  		eventURL, err := event.URLFromID(s.baseURL, e.ID)
   110  		if err != nil {
   111  			return nil, fmt.Errorf("invalid event url: %s", err)
   112  		}
   113  		feed.Channel.Items = append(feed.Channel.Items, Item{
   114  			Title:       e.Name,
   115  			Link:        eventURL,
   116  			Description: e.Description,
   117  			Guid:        e.ID,
   118  		})
   119  	}
   120  
   121  	return feed, nil
   122  }
   123  
   124  func (s *ServiceImpl) GroupFeed(ctx context.Context, groupID string) (*Feed, error) {
   125  	groupURL, err := group.URLFromID(s.baseURL, groupID)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("invalid group url: %s", err)
   128  	}
   129  	g, err := s.groupService.FindByID(ctx, groupID)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	return s.Feed(
   134  		ctx,
   135  		eventsearch.Options{Filters: []eventsearch.Filter{eventsearch.GroupIDFilter{GroupID: groupID}}},
   136  		&Channel{Title: g.Name, Link: groupURL + "/rss", Description: g.Description},
   137  	)
   138  }
   139  
   140  func (s *ServiceImpl) PlaceFeed(ctx context.Context, placeID string) (*Feed, error) {
   141  	placeURL, err := place.URLFromID(s.baseURL, placeID)
   142  	if err != nil {
   143  		return nil, fmt.Errorf("invalid group url: %s", err)
   144  	}
   145  	p, err := s.placeService.FindByID(ctx, placeID)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	return s.Feed(
   150  		ctx,
   151  		eventsearch.Options{Filters: []eventsearch.Filter{eventsearch.PlaceIDFilter{PlaceID: placeID}}},
   152  		&Channel{Title: p.Name, Link: placeURL + "/rss", Description: p.Description},
   153  	)
   154  }