service

package
v0.241.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: AGPL-3.0 Imports: 63 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var LoadElasticService = sync.OnceValues(func() (*ElasticService, error) {
	appCfg, err := config.LoadAppConfig()
	if err != nil {
		return nil, fmt.Errorf("load app config: %w", err)
	}
	return &ElasticService{
		environment: appCfg.GetAppEnvironment(),
	}, nil
})
View Source
var LoadFeedService = sync.OnceValues(func() (*FeedService, error) {
	svc, err := LoadElasticService()
	if err != nil {
		return nil, fmt.Errorf("load elastic service: %w", err)
	}
	return &FeedService{
		Cache: otter.Must(&otter.Options[models.FeedID, *models.Feed]{
			MaximumSize:      30_000,
			ExpiryCalculator: &feedCacheExpiryCalculator{},
		}),
		store: svc,
		loader: otter.LoaderFunc[models.FeedID, *models.Feed](
			func(ctx context.Context, id models.FeedID) (*models.Feed, error) {
				index := ctx.Value("feeds_index").(string)
				feed, err := elastic.GetDoc[models.FeedID, *models.Feed](ctx, index, id)
				if err != nil {
					return nil, fmt.Errorf("%w: %w", otter.ErrNotFound, err)
				}
				return feed, nil
			},
		),
		httpClient: newHTTPClient(),
	}, nil
})

LoadFeedService loads a service that can manipulate feed objects in the backend store.

View Source
var LoadItemService = sync.OnceValues(func() (*ItemService, error) {
	svc, err := LoadElasticService()
	if err != nil {
		return nil, fmt.Errorf("load elastic service: %w", err)
	}
	return &ItemService{
		Cache: otter.Must(&otter.Options[models.ItemID, *models.Item]{
			MaximumSize:      10_000,
			ExpiryCalculator: &itemCacheexpiryCalculator{},
		}),
		store:      svc,
		httpClient: newHTTPClient(),
	}, nil
})

LoadItemService loads a service that can manipulate item objects in the backend store.

View Source
var LoadSubscriptionService = sync.OnceValues(func() (*SubscriptionService, error) {
	svc, err := LoadElasticService()
	if err != nil {
		return nil, fmt.Errorf("load elastic service: %w", err)
	}

	feedSvc, err := LoadFeedService()
	if err != nil {
		return nil, fmt.Errorf("load feed service: %w", err)
	}

	return &SubscriptionService{
		Cache: otter.Must(
			&otter.Options[models.UserID, *UserSubscriptions]{
				MaximumSize:      100,
				ExpiryCalculator: otter.ExpiryAccessing[models.UserID, *UserSubscriptions](time.Hour),
			},
		),
		store: svc,
		userLoader: otter.LoaderFunc[models.UserID, *UserSubscriptions](
			func(
				ctx context.Context,
				userID models.UserID,
			) (*UserSubscriptions, error) {
				userSubscriptionsCache, err := otter.New(&otter.Options[models.SubscriptionID, *models.Subscription]{
					InitialCapacity: 3000,
					MaximumSize:     3000,
				})
				if err != nil {
					return nil, fmt.Errorf("create subscriptions cache: %w", err)
				}
				start := time.Now()
				// Execute query.
				var (
					subscriptions models.Subscriptions
				)
				subscriptions, err = elastic.SearchAll[*models.Subscription](
					ctx,
					svc.GetIndexRO(SubscriptionsIndex),
					query.Term("user_id", userID),
					3000,
				)
				if err != nil {
					return nil, ElasticsearchToAPIError(err)
				}

				if len(subscriptions) == 0 {
					return nil, otter.ErrNotFound
				}

				feeds, err := feedSvc.GetFeeds(ctx, subscriptions.GetFeedIDs()...)
				if err != nil {
					return nil, fmt.Errorf("get feeds for subscriptions: %w", err)
				}

				for groupSubscription := range slices.Values(subscriptions.FilterByType(models.SubscriptionTypeGroup)) {
					groupSubscription.GroupData.Subscriptions = make(
						[]*models.Subscription,
						0,
						len(groupSubscription.GroupData.Metadata),
					)
					for m := range slices.Values(groupSubscription.GroupData.Metadata) {
						grouped := subscriptions.GetByID(m.SubscriptionID)
						groupSubscription.GroupData.Subscriptions = append(
							groupSubscription.GroupData.Subscriptions,
							grouped,
						)
					}
				}

				for subscription := range slices.Values(subscriptions) {

					subscription.Feed = feeds.FindByID(subscription.GetFeedID())

					userSubscriptionsCache.Set(subscription.GetID(), subscription)
				}

				slogctx.Debug(ctx, "Created subscriptions cache for user.",
					slog.Duration("took", time.Since(start)))

				return &UserSubscriptions{
					Cache:  userSubscriptionsCache,
					userID: userID,
					loader: otter.LoaderFunc[models.SubscriptionID, *models.Subscription](
						func(ctx context.Context, id models.SubscriptionID) (*models.Subscription, error) {
							subscription, err := elastic.GetDoc[models.SubscriptionID, *models.Subscription](
								ctx,
								svc.GetIndexRO(SubscriptionsIndex),
								id,
							)
							if err != nil && !errors.Is(err, elastic.ErrNotFound) {
								return nil, fmt.Errorf("get subscriptions: %w", ElasticsearchToAPIError(err))
							}
							if errors.Is(err, elastic.ErrNotFound) {
								return nil, otter.ErrNotFound
							}

							feed, err := feedSvc.GetFeed(ctx, subscription.GetFeedID())
							if err != nil {
								return nil, fmt.Errorf("get feed for subscriptions: %w", err)
							}
							subscription.Feed = feed

							if subscription.Type == models.SubscriptionTypeGroup {
								grouped, err := elastic.GetDocs[models.SubscriptionID, *models.Subscription](
									ctx,
									svc.GetIndexRO(SubscriptionsIndex),
									subscription.GroupData.GetGroupedSubscriptionIDs()...,
								)
								if err != nil {
									return nil, fmt.Errorf("get grouped subscriptions: %w", err)
								}
								subscription.GroupData.Subscriptions = grouped
							}
							return subscription, nil
						}),
					bulkLoader: otter.BulkLoaderFunc[models.SubscriptionID, *models.Subscription](
						func(
							ctx context.Context,
							ids []models.SubscriptionID,
						) (map[models.SubscriptionID]*models.Subscription, error) {
							var (
								subscriptions models.Subscriptions
								err           error
							)
							subscriptions, err = elastic.GetDocs[models.SubscriptionID, *models.Subscription](
								ctx,
								svc.GetIndexRO(SubscriptionsIndex),
								ids...,
							)
							if err != nil {
								return nil, fmt.Errorf("get subscriptions: %w", ElasticsearchToAPIError(err))
							}

							feeds, err := feedSvc.GetFeeds(ctx, subscriptions.GetFeedIDs()...)
							if err != nil {
								return nil, fmt.Errorf("get feed for subscriptions: %w", err)
							}

							results := make(map[models.SubscriptionID]*models.Subscription, len(subscriptions))

							for subscription := range slices.Values(subscriptions) {

								subscription.Feed = feeds.FindByID(subscription.GetFeedID())

								if subscription.Type == models.SubscriptionTypeGroup {
									grouped, err := elastic.GetDocs[models.SubscriptionID, *models.Subscription](
										ctx,
										svc.GetIndexRO(SubscriptionsIndex),
										subscription.GroupData.GetGroupedSubscriptionIDs()...,
									)
									if err != nil {
										return nil, fmt.Errorf("get grouped subscriptions: %w", err)
									}
									subscription.GroupData.Subscriptions = grouped
								}
								results[subscription.GetID()] = subscription
							}

							return results, nil
						}),
				}, nil
			}),
	}, nil
})

LoadSubscriptionService loads a service that can manipulate subscription objects in the backend store.

View Source
var LoadUserService = sync.OnceValues(func() (*UserService, error) {
	svc, err := LoadElasticService()
	if err != nil {
		return nil, fmt.Errorf("load elastic service: %w", err)
	}
	return &UserService{
		Cache: otter.Must(&otter.Options[string, models.User]{
			MaximumSize: 100,
			ExpiryCalculator: otter.ExpiryAccessing[string, models.User](
				60 * time.Second,
			),
		}),
		store: svc,
		loader: otter.LoaderFunc[string, models.User](
			func(ctx context.Context, id string) (models.User, error) {
				index := ctx.Value("users_index").(string)
				switch resp, err := elastic.Search[*models.User](ctx,
					index,
					elastic.WithQuery[*search.Search](
						query.Term("external_user_id", id, query.WithQueryName[*query.TermQuery]("get-user-by-external-id")),
					),
					elastic.WithDocSorting[*search.Search](),
					elastic.WithTrackHits[*search.Search](false),
					elastic.WithSize[*search.Search](1),
				); {
				case err != nil:
					return models.User{}, fmt.Errorf("%w: %w", otter.ErrNotFound, err)
				case len(resp.Results) == 0:
					return models.User{}, fmt.Errorf("%w: %w", otter.ErrNotFound, elastic.ErrNotFound)
				default:
					return *resp.Results[0], nil
				}
			},
		),
	}, nil
})

LoadUserService loads a service that can manipulate user objects in the backend store.

Functions

func ArticleFiltersQueryClause added in v0.210.3

func ArticleFiltersQueryClause(filters *models.ArticleFilters) query.BoolOption

func BuildItemQueries

func BuildItemQueries(
	user *models.User,
	view models.View,
	subscriptions models.Subscriptions,
) []query.Option

BuildItemQueries generates a slices of queries for the given subscriptions, based on the given filters.

func ClassifyFeed added in v0.189.0

func ClassifyFeed(ctx context.Context, feed *models.Feed) error

ClassifyFeed will add appropriate categories to a feed by classifying the item content.

func DiscoverFeedURL added in v0.190.1

func DiscoverFeedURL(sourceURL *url.URL, content []byte) (string, error)

DiscoverFeedURL attempts to find a feed URL within a HTML page.

There are a couple of "canonical" places the feed URL is located. Firstly, as per the RSS spec, look for a link element with rel="alternate" and type="application/rss+xml". Secondly, check for a link element with a URL that ends with feed, rss or atom, which would indicate a feed URL.

func EditEmailSubscription added in v0.222.0

func EditEmailSubscription(
	ctx context.Context,
	subscription *models.Subscription,
	edits *models.EditEmailSubscriptionRequest,
) error

func EditFeedSubscription added in v0.222.0

func EditFeedSubscription(
	ctx context.Context,
	subscription *models.Subscription,
	edits *models.FeedSubscriptionRequest,
) error

func EditGroupSubscription added in v0.222.0

func EditGroupSubscription(
	ctx context.Context,
	subscription *models.Subscription,
	edits *models.GroupSubscriptionRequest,
) error

func EditSearchSubscription added in v0.222.0

func EditSearchSubscription(
	ctx context.Context,
	subscription *models.Subscription,
	edits *models.SearchSubscriptionRequest,
) error

func ElasticsearchToAPIError added in v0.132.2

func ElasticsearchToAPIError(err error) error

ElasticsearchToAPIError will extract and wrap a types.ElasticsearchError from the given error, in an APIError containing its pertinent information. If the given error does not contain types.ElasticsearchError, the given error is wrapped in a generic APIError is created.

func GenerateArticles added in v0.128.0

func GenerateArticles(ctx context.Context, items models.Items) (models.Articles, error)

GenerateArticles takes a slice of items and creates articles from them, grabbing the necessary data from the user object.

func GetArticleRemoteContent added in v0.147.0

func GetArticleRemoteContent(
	ctx context.Context,
	httpClient *resty.Client,
	itemPageCache cache.ObjectCache,
	article *models.Article,
) error

GetArticleRemoteContent populates the article content with the item source.

func NewEmailSubscription added in v0.222.0

func NewEmailSubscription(
	ctx context.Context,
	userID models.UserID,
	from *mail.Address,
) (*models.Subscription, error)

func NewFeedFromZyteResponse added in v0.222.0

func NewFeedFromZyteResponse(ctx context.Context, resp *zyte.Response) (*models.Feed, error)

func NewFeedSortCombinations added in v0.160.0

func NewFeedSortCombinations(sort *models.Sort) []estypes.SortCombinations

func NewFeedSortOptions added in v0.160.0

func NewFeedSortOptions(sort *models.Sort) []estypes.SortCombinationsVariant

func NewFeedSubscription added in v0.222.0

func NewFeedSubscription(
	ctx context.Context,
	feed *models.Feed,
	customisation *models.SubscriptionCustomisation,
) (*models.Subscription, error)

NewFeedSubscription creates a new subscription for a feed with any user customisations given.

func NewGroupSubscription added in v0.222.0

func NewGroupSubscription(ctx context.Context, request *models.GroupSubscriptionRequest) (*models.Subscription, error)

NewGroupSubscription creates a GroupSubscription. A GroupSubscription is a kind of meta-subscription that aggregates all articles from multiple individual subscriptions into a single custom subscription.

func NewItemSortCombinations added in v0.132.2

func NewItemSortCombinations(sort *models.Sort) []estypes.SortCombinations

func NewItemSortOptions added in v0.132.2

func NewItemSortOptions(sort *models.Sort) []estypes.SortCombinationsVariant

func NewItemsFromZyteArticles added in v0.222.0

func NewItemsFromZyteArticles(
	ctx context.Context,
	feed *models.Feed,
	articles *zyte.ArticleList,
) (models.Items, error)

func NewSearchSubscription added in v0.222.0

func NewSearchSubscription(
	ctx context.Context,
	request *models.SearchSubscriptionRequest,
) (*models.Subscription, error)

NewSearchSubscription creates a new SearchSubscription. A SearchSubscription collates articles that match a search into a single custom subscription.

Types

type Change added in v0.223.0

type Change struct {
	Old any `json:"old"`
	New any `json:"new"`
}

type ElasticService added in v0.235.0

type ElasticService struct {
	// contains filtered or unexported fields
}

func (*ElasticService) GetIndexRO added in v0.235.0

func (s *ElasticService) GetIndexRO(name Index) string

func (*ElasticService) GetIndexRW added in v0.235.0

func (s *ElasticService) GetIndexRW(name Index) string

type FeedService added in v0.235.0

type FeedService struct {
	*otter.Cache[models.FeedID, *models.Feed]
	// contains filtered or unexported fields
}

FeedService holds a cache and backend connection for handling models.Feed objects.

func (*FeedService) AddFeed added in v0.235.0

func (s *FeedService) AddFeed(ctx context.Context, feed *models.Feed) error

AddFeed adds a new feed to Elasticsearch and the cache.

func (*FeedService) ApplyFeedUpdates added in v0.235.0

func (s *FeedService) ApplyFeedUpdates(ctx context.Context,
	itemPageCache cache.ObjectCache,
	oldData, newData *models.Feed) error

ApplyFeedUpdates takes an existing feed and new feed data, compares the two, updates any fields as appropriate and writes the updated feed back to the database. It will add/update both any new/updated items and any updates to the feed metadata.

func (*FeedService) FetchFeed added in v0.239.1

func (s *FeedService) FetchFeed(
	ctx context.Context,
	feedURL string,
	options ...FetchOption,
) (*models.Feed, error)

FetchFeed retrieves the feed found at the given URL.

func (*FeedService) FetchFeedUpdates added in v0.239.1

func (s *FeedService) FetchFeedUpdates(
	ctx context.Context,
	details *models.Feed,
) (*models.Feed, models.URL, error)

FetchFeedUpdates fetches an updated version of the feed, including items. It returns the updated feed, and the source URL used to fetch the updates (for disambiguation of feeds with multiple source URLs). A non-nil error is returned where there is a critical error fetching the feed details.

func (*FeedService) FetchFeedUpdatesAsArticles added in v0.239.1

func (s *FeedService) FetchFeedUpdatesAsArticles(
	ctx context.Context,
	details *models.Feed,
) (*models.Feed, models.URL, error)

FetchFeedUpdatesAsArticles fetches an updated version of the feed, using Zyte to generate a feed from the source website articles list. It returns the updated feed, and the source URL used to fetch the updates (for disambiguation of feeds with multiple source URLs). A non-nil error is returned where there is a critical error fetching the feed details.

func (*FeedService) FindOrCreateFeed added in v0.235.0

func (s *FeedService) FindOrCreateFeed(
	ctx context.Context,
	feedURL string,
) (*models.Feed, bool, error)

FindOrCreateFeed will either generate a new feed or return the existing feed for the given URL. If the feed is new, the boolean return value will be true.

func (*FeedService) GenerateOPML added in v0.235.0

func (s *FeedService) GenerateOPML(ctx context.Context, feedIDs ...models.FeedID) ([]byte, error)

GenerateOPML generates an OPML file of the feeds listed by the given IDs.

func (*FeedService) GetAllFeedsExcept added in v0.235.0

func (s *FeedService) GetAllFeedsExcept(ctx context.Context, ids ...models.FeedID) (models.Feeds, error)

GetAllFeedsExcept retrieves a models.Feeds slice containing all feeds except those with the given models.FeedID.

func (*FeedService) GetFeed added in v0.235.0

func (s *FeedService) GetFeed(ctx context.Context, id models.FeedID) (*models.Feed, error)

GetFeed retrieves a feed with the given FeedID.

func (*FeedService) GetFeeds added in v0.235.0

func (s *FeedService) GetFeeds(ctx context.Context, ids ...models.FeedID) (models.Feeds, error)

GetFeeds retrieves the Feeds matching the given FeedIDs. It will fetch any cached versions before fetching from Elasticsearch (and then caching those).

func (FeedService) GetFeedsByURLs added in v0.237.0

func (s FeedService) GetFeedsByURLs(ctx context.Context, urls ...string) (models.Feeds, error)

GetFeedByURLs retrieves a models.Feeds slice containing all feeds that have a source matching one of the given URLs.

func (*FeedService) GetNewFeedsSince added in v0.235.0

func (s *FeedService) GetNewFeedsSince(ctx context.Context, ts time.Time) (models.Feeds, error)

GetNewFeedsSince retrieves a models.Feeds slice containing all feeds created since the given timestamp.

func (*FeedService) SuggestFeeds added in v0.235.0

func (s *FeedService) SuggestFeeds(
	ctx context.Context,
	request *models.SuggestFeedsRequest,
) (*models.SuggestFeedsResults, error)

SuggestFeeds returns a feeds and their latest articles that match the given text. It will search first for existing feeds in Elasticsearch. If the given text is a URL, it will fallback to searching the website for a feed.

func (*FeedService) SuggestGoogleNewsFeeds added in v0.235.0

func (s *FeedService) SuggestGoogleNewsFeeds(
	ctx context.Context,
	text string,
) (*models.SuggestFeedsResults, error)

SuggestGoogleNewsFeeds will return a google news RSS feed for the given search query.

func (*FeedService) SuggestYoutubeFeeds added in v0.235.0

func (s *FeedService) SuggestYoutubeFeeds(ctx context.Context, text string) (*models.SuggestFeedsResults, error)

SuggestYoutubeFeeds will return a list of youtube feeds that match the given text.

func (*FeedService) UpdateFeed added in v0.235.0

func (s *FeedService) UpdateFeed(ctx context.Context, feed *models.Feed) error

UpdateFeed applies the given updates to a Feed. Any cached version of the feed is invalidated.

func (*FeedService) UpdateFeedItems added in v0.235.0

func (s *FeedService) UpdateFeedItems(
	ctx context.Context,
	itemPageCache cache.ObjectCache,
	oldData, newData *models.Feed,
) (time.Time, error)

UpdateFeedItems determines if there are new items in the feed and then adds them to the database, performing item enrichment as needed. It returns a timestamp indicating a new lastFetched value for the feed, based off the latest new item's timestamp.

type FeedSorting added in v0.160.0

type FeedSorting struct {
	Updated   string `json:"updated"`
	Published string `json:"published"`
	FeedID    string `json:"feed_id"`
}

FeedSorting contains the sort options for sorting item search results.

func (*FeedSorting) SortCombinationsCaster added in v0.160.0

func (s *FeedSorting) SortCombinationsCaster() *estypes.SortCombinations

SortCombinationsCaster is required to allow FeedSorting to be used as Elasticsearch sort values.

type FetchOption added in v0.148.1

type FetchOption func(*FetchOptions)

FetchOption is a functional option that controls some aspect of how a feed will be fetched.

func FetchWithFeedID added in v0.147.0

func FetchWithFeedID(id models.FeedID) FetchOption

FetchWithFeedID assigns the given FeedID to the feed object.

func FetchWithProxy added in v0.147.0

func FetchWithProxy(value bool) FetchOption

FetchWithProxy option will ensure the fetch feed request is proxied.

type FetchOptions added in v0.147.0

type FetchOptions struct {
	Proxy  bool
	FeedID models.FeedID
}

FetchOptions defines options that manipulate how a feed will be fetched from a remote URL.

type Home added in v0.235.0

type Home struct{}

func (*Home) AggregateSubscriptions added in v0.235.0

func (s *Home) AggregateSubscriptions(
	ctx context.Context,
	user *models.User,
	subscriptions models.Subscriptions,
) (models.CategoryCounts, models.CategoryCounts, models.Articles, error)

type ImportService added in v0.237.0

type ImportService struct {
	// contains filtered or unexported fields
}

func NewImportService added in v0.237.0

func NewImportService() (*ImportService, error)

func (*ImportService) GetAllImports added in v0.238.0

func (i *ImportService) GetAllImports(ctx context.Context, userID models.UserID) ([]*models.ImportStatus, error)

func (*ImportService) GetImportStatus added in v0.237.0

func (i *ImportService) GetImportStatus(
	ctx context.Context,
	jobID string,
) (*models.ImportStatus, []*models.ImportResult, error)

func (*ImportService) GetPendingImports added in v0.237.0

func (i *ImportService) GetPendingImports(ctx context.Context) ([]*models.ImportStatus, error)

GetPendingImports retrieves any imports in "pending" status (i.e., ready to be started).

func (*ImportService) ProcessRequests added in v0.237.0

func (i *ImportService) ProcessRequests(
	ctx context.Context,
	status *models.ImportStatus,
) error

bulkImportFeeds handles processing any number of NewFeedSubscriptionRequest requests.

func (*ImportService) StartImport added in v0.237.0

func (i *ImportService) StartImport(
	ctx context.Context,
	file *models.OPMLFile,
) (string, error)

StartImport generates a models.ImportStatus with a slice of models.ImportRequest for all feeds found in the given OPML file. An import job run by the scheduler will find the status and process it.

type Index added in v0.235.0

type Index string
const (
	FeedsIndex         Index = "feeds"
	ItemsIndex         Index = "items"
	FavoritesIndex     Index = "favorites"
	UsersIndex         Index = "users"
	SubscriptionsIndex Index = "subscriptions"
	ScheduleIndex      Index = "scheduler"
	SessionsIndex      Index = "sessions"
	ImportIndex        Index = "imports"
)

type ItemService added in v0.235.0

type ItemService struct {
	*otter.Cache[models.ItemID, *models.Item]
	// contains filtered or unexported fields
}

ItemService holds a cache and backend connection for handling models.Item objects.

func (*ItemService) AddItems added in v0.235.0

func (s *ItemService) AddItems(ctx context.Context, items models.Items) (map[string]models.Items, error)

AddItems will add the given items to the database. It returns a map divided into "updated" and "new" items, to indicate items that existed and were updated vs. items that were added as new.

func (*ItemService) ArchiveArticle added in v0.235.0

func (s *ItemService) ArchiveArticle(ctx context.Context, article *models.ArticleArchive) error

archiveArticle will index the given article content to the article archive for permanent storage.

func (*ItemService) CountItems added in v0.235.0

func (s *ItemService) CountItems(ctx context.Context, query query.Option) (int64, error)

CountItems returns a count of items that match the given query.

func (*ItemService) CountSearchResults added in v0.235.0

func (s *ItemService) CountSearchResults(ctx context.Context, request *models.SearchRequest) (int64, error)

CountSearchResults will return an approximate count of the number of items that would match the given *models.SearchRequest, published within the last 5 minutes.

func (*ItemService) EnrichItem added in v0.239.1

func (s *ItemService) EnrichItem(
	ctx context.Context,
	itemPageCache cache.ObjectCache,
	feed *models.Feed,
	item *models.Item,
) error

EnrichItem checks the item data if it is missing certain values, flags it, then tries to enrich the item to fill missing data from the item source.

func (*ItemService) FilterArticles added in v0.235.0

func (s *ItemService) FilterArticles(
	ctx context.Context,
	request *models.ListRequest,
) (models.Articles, models.Pagination, error)

FilterArticles returns Articles filtered by the given filters and paginated by the given pagination.

func (*ItemService) FindSimilarArticles added in v0.235.0

func (s *ItemService) FindSimilarArticles(
	ctx context.Context,
	count int,
	itemIDs ...models.ItemID,
) (models.Articles, error)

FindSimilarArticles performs a "more like this" search to find other Articles that are similar to the Items with the given IDs.

func (*ItemService) GetArticles added in v0.235.0

func (s *ItemService) GetArticles(
	ctx context.Context,
	itemIDs ...models.ItemID,
) (models.Articles, error)

GetArticles generates Article objects from the Items with the given IDs.

func (*ItemService) GetItems added in v0.235.0

func (s *ItemService) GetItems(ctx context.Context, ids ...models.ItemID) (models.Items, error)

GetItems retrieves the Items matching the given ItemIDs.

func (*ItemService) GetNextArticle added in v0.235.0

func (s *ItemService) GetNextArticle(
	ctx context.Context,
	currentID models.ItemID,
	subscriptionID models.SubscriptionID,
	view models.View,
	direction string,
	ts time.Time,
) (*models.Article, error)

GetNextArticle returns the "next" article from the given article, based on the given article timestamp. The direction defines what the next article will be (previous or next). If a subscription is given, it will filter to that subscription only. Otherwise, the results are also filtered to the given view.

func (*ItemService) GetTopCategoriesForItems added in v0.235.0

func (s *ItemService) GetTopCategoriesForItems(
	ctx context.Context,
	itemsQuery query.Option,
) (models.CategoryCounts, error)

func (*ItemService) GetTopItemCategoriesForSearchResults added in v0.235.0

func (s *ItemService) GetTopItemCategoriesForSearchResults(
	ctx context.Context,
	request *models.SearchRequest,
) (models.Categories, error)

GetTopItemCategoriesForSearchResults returns a models.Categories slice containing the top categories from items that match the given *models.SearchRequest.

func (*ItemService) GetTopLanguageCountsForItems added in v0.239.0

func (s *ItemService) GetTopLanguageCountsForItems(
	ctx context.Context,
	itemsQuery query.Option,
) (map[string]int64, error)

func (*ItemService) QueryItems added in v0.235.0

func (s *ItemService) QueryItems(
	ctx context.Context,
	query query.Option,
	count int,
	sort *models.Sort,
	pagination *string,
) (models.Items, string, error)

SearchItems will search the items index for items matching the given query. Count, sort and pagination values are optional.

func (*ItemService) RetrieveItems added in v0.235.0

func (s *ItemService) RetrieveItems(
	ctx context.Context,
	request *models.SearchRequest,
) (models.Items, models.Pagination, error)

RetrieveItems will retrieve a models.Items slice of items matching the given *models.SearchRequest. The request supports pagination and will return the next set of results as appropriate.

func (*ItemService) SuggestItems added in v0.235.0

func (s *ItemService) SuggestItems(
	ctx context.Context,
	request *models.SearchRequest,
) (models.Items, error)

func (*ItemService) UnarchiveArticle added in v0.235.0

func (s *ItemService) UnarchiveArticle(
	ctx context.Context,
	userID models.UserID,
	itemID models.ItemID,
) error

unarchiveArticle will delete an article from the archive.

type ItemSorting added in v0.132.2

type ItemSorting struct {
	Published string `json:"published"`
	Updated   string `json:"updated"`
	ItemID    string `json:"item_id"`
}

ItemSorting contains the sort options for sorting item search results.

func (*ItemSorting) SortCombinationsCaster added in v0.132.2

func (s *ItemSorting) SortCombinationsCaster() *estypes.SortCombinations

SortCombinationsCaster is required to allow ItemSorting to be used as Elasticsearch sort values.

type SubscriptionService added in v0.235.0

type SubscriptionService struct {
	*otter.Cache[models.UserID, *UserSubscriptions]
	// contains filtered or unexported fields
}

SubscriptionService holds a cache and backend connection for handling models.Subscription objects.

func (*SubscriptionService) AddSubscriptions added in v0.237.0

func (s *SubscriptionService) AddSubscriptions(
	ctx context.Context,
	newSubscriptions ...*models.Subscription,
) error

AddSubscriptions adds the given subscriptions to a user.

func (*SubscriptionService) BulkGetSubscriptions added in v0.235.0

func (s *SubscriptionService) BulkGetSubscriptions(
	ctx context.Context,
	ids ...models.SubscriptionID,
) (models.Subscriptions, error)

BulkGetSubscriptions returns a models.Subscriptions slice of subscriptions that match the given models.SubscriptionID.

func (*SubscriptionService) GetAllSubscriptions added in v0.235.0

func (s *SubscriptionService) GetAllSubscriptions(
	ctx context.Context,
) (models.Subscriptions, error)

GetAllSubscriptions returns a models.Subscriptions slice of all subscriptions for a user.

func (*SubscriptionService) GetLatestArticles added in v0.235.0

func (s *SubscriptionService) GetLatestArticles(
	ctx context.Context,
	view models.View,
	subscriptions models.Subscriptions,
)

GetLatestArticles will fetch and add the latest articles to the given subscriptions.

func (*SubscriptionService) GetSubscription added in v0.235.0

func (s *SubscriptionService) GetSubscription(
	ctx context.Context,
	id models.SubscriptionID,
) (*models.Subscription, error)

GetSubscription returns a *models.Subscription that matches the given models.SubscriptionID for the given user.

func (*SubscriptionService) GetSubscriptionCategorySuggestions added in v0.235.0

func (s *SubscriptionService) GetSubscriptionCategorySuggestions(
	ctx context.Context,
	feedIDs []models.FeedID,
	excludedCategories []models.Category,
) []models.Category

func (*SubscriptionService) GetSubscriptionSuggestions added in v0.235.0

func (s *SubscriptionService) GetSubscriptionSuggestions(
	ctx context.Context,
	text string,
	count int,
	ignoredSubscriptions []models.SubscriptionID,
) (models.Subscriptions, error)

GetSubscriptionSuggestions returns subscriptions that match the given text. A set of ids can be optionally passed to ignore those subscriptions.

func (*SubscriptionService) Invalidate added in v0.237.0

func (s *SubscriptionService) Invalidate(userID models.UserID)

Invalidate will invalidate the cache for the given user. Can be used to force fetching subscriptions from the backend.

func (*SubscriptionService) MarkArticles added in v0.235.0

func (s *SubscriptionService) MarkArticles(
	ctx context.Context,
	mark models.Mark,
	subscriptionID models.SubscriptionID,
	itemIDs ...models.ItemID,
) error

func (*SubscriptionService) MarkSubscriptions added in v0.235.0

func (s *SubscriptionService) MarkSubscriptions(
	ctx context.Context,
	mark models.Mark,
	subscriptionIDs ...models.SubscriptionID,
) error

MarkSubscriptions will mark as appropriate all the given subscriptions. Marking a subscription includes updating the subscription data in the user object and clearing any individual item states for a subscription.

func (*SubscriptionService) RemoveSubscriptions added in v0.235.0

func (s *SubscriptionService) RemoveSubscriptions(
	ctx context.Context,
	ids ...models.SubscriptionID,
) error

RemoveSubscriptions removes subscriptions with the given models.SubscriptionID from a user.

func (*SubscriptionService) UpdateSubscriptionDynamicInfo added in v0.235.0

func (s *SubscriptionService) UpdateSubscriptionDynamicInfo(
	ctx context.Context,
	subscriptions models.Subscriptions,
) error

UpdateSubscriptionDynamicInfo adds dynamically generated information (e.g., unread count, stats, etc.) of the subscriptions in the models.Subscriptions slice. At the least, all subscriptions will have an unread count and last updated info generated. Other stats will also be generated if the user has set the display option ShowSubscriptionStats in their account settings.

func (*SubscriptionService) UpdateSubscriptions added in v0.235.0

func (s *SubscriptionService) UpdateSubscriptions(
	ctx context.Context,
	subscriptions ...*models.Subscription,
) error

UpdateSubscriptions will bulk update each given *models.Subscription.

type SubscriptionSorting added in v0.128.0

type SubscriptionSorting struct {
	MarkedReadAt   string `json:"marked_read_at"`
	SubscriptionID string `json:"subscription_id"`
}

SubscriptionSorting contains the sort options for sorting subscription results.

func (*SubscriptionSorting) SortCombinationsCaster added in v0.128.0

func (s *SubscriptionSorting) SortCombinationsCaster() *estypes.SortCombinations

SortCombinationsCaster is required to allow FeedSorting to be used as Elasticsearch sort values.

type UserService added in v0.235.0

type UserService struct {
	*otter.Cache[string, models.User]
	// contains filtered or unexported fields
}

UserService holds a cache and backend connection for handling models.User objects.

func (UserService) AddUser added in v0.235.0

func (s UserService) AddUser(ctx context.Context, user *models.User) error

AddUser stores and caches the given *models.User in the backend.

func (*UserService) DeleteUser added in v0.235.0

func (s *UserService) DeleteUser(ctx context.Context, user *models.User) error

DeleteUser deletes the user and the user's subscription objects from Elasticsearch.

func (*UserService) GetAllUsers added in v0.235.0

func (s *UserService) GetAllUsers(ctx context.Context) ([]*models.User, error)

GetUser retrieves the user doc with the given id.

func (*UserService) GetUser added in v0.235.0

func (s *UserService) GetUser(ctx context.Context, id models.UserID) (*models.User, error)

GetUser retrieves the user doc with the given id.

func (*UserService) GetUserByCustomerID added in v0.235.0

func (s *UserService) GetUserByCustomerID(ctx context.Context, id string) (*models.User, error)

GetUserByCustomerID retrieves the user associated with the given customer ID. It handles finding and adding customer details to an existing user for a new customer.

func (*UserService) GetUserByEmail added in v0.235.0

func (s *UserService) GetUserByEmail(ctx context.Context, email string) (*models.User, error)

GetUserByEmail will retrieve a user by their email.

func (*UserService) GetUserByExternalID added in v0.235.0

func (s *UserService) GetUserByExternalID(ctx context.Context, externalID string) (*models.User, error)

GetUserByExternalID will search for and return a user that matches the given external ID, if exists.

func (*UserService) GetUserByPurchaseToken added in v0.235.0

func (s *UserService) GetUserByPurchaseToken(ctx context.Context, token string) (*models.User, error)

getUserByPurchaseToken retrieves the user associated with the given purchase token.

func (*UserService) GetUserBySubscriptionEmail added in v0.235.0

func (s *UserService) GetUserBySubscriptionEmail(ctx context.Context, emails ...string) (*models.User, error)

GetUserBySubscriptionEmail will retrieve a user from their Foragd newsletter subscription email.

func (*UserService) GetUserBySubscriptionID added in v0.235.0

func (s *UserService) GetUserBySubscriptionID(ctx context.Context, id string) (*models.User, error)

GetUserBySubscriptionID will retrieve a user from their payment subscription ID.

func (*UserService) UpdateUser added in v0.235.0

func (s *UserService) UpdateUser(ctx context.Context, user *models.User, updates map[string]any) error

UpdateUser will apply the given updates to the user.

type UserSubscriptions added in v0.235.0

type UserSubscriptions struct {
	*otter.Cache[models.SubscriptionID, *models.Subscription]
	// contains filtered or unexported fields
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL