From bc20e0884b3ca051ae77e1bb6e2de11419d36d4d Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Sun, 3 Dec 2017 17:44:27 -0800 Subject: Add Fever API --- storage/entry_query_builder.go | 100 ++++++++++++++++++++++++++++++++++++----- storage/icon.go | 31 +++++++++++++ storage/integration.go | 44 ++++++++++++++++-- 3 files changed, 160 insertions(+), 15 deletions(-) (limited to 'storage') diff --git a/storage/entry_query_builder.go b/storage/entry_query_builder.go index 6f7f4bd..143fd3b 100644 --- a/storage/entry_query_builder.go +++ b/storage/entry_query_builder.go @@ -9,24 +9,47 @@ import ( "strings" "time" + "github.com/lib/pq" + "github.com/miniflux/miniflux2/helper" "github.com/miniflux/miniflux2/model" ) // EntryQueryBuilder builds a SQL query to fetch entries. type EntryQueryBuilder struct { - store *Storage - feedID int64 - userID int64 - timezone string - categoryID int64 - status string - notStatus string - order string - direction string - limit int - offset int - entryID int64 + store *Storage + feedID int64 + userID int64 + timezone string + categoryID int64 + status string + notStatus string + order string + direction string + limit int + offset int + entryID int64 + greaterThanEntryID int64 + entryIDs []int64 + before *time.Time +} + +// Before add condition base on the entry date. +func (e *EntryQueryBuilder) Before(date *time.Time) *EntryQueryBuilder { + e.before = date + return e +} + +// WithGreaterThanEntryID adds a condition > entryID. +func (e *EntryQueryBuilder) WithGreaterThanEntryID(entryID int64) *EntryQueryBuilder { + e.greaterThanEntryID = entryID + return e +} + +// WithEntryIDs adds a condition to fetch only the given entry IDs. +func (e *EntryQueryBuilder) WithEntryIDs(entryIDs []int64) *EntryQueryBuilder { + e.entryIDs = entryIDs + return e } // WithEntryID set the entryID. @@ -195,6 +218,44 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) { return entries, nil } +// GetEntryIDs returns a list of entry IDs that match the condition. +func (e *EntryQueryBuilder) GetEntryIDs() ([]int64, error) { + debugStr := "[EntryQueryBuilder:GetEntryIDs] userID=%d, feedID=%d, categoryID=%d, status=%s, order=%s, direction=%s, offset=%d, limit=%d" + defer helper.ExecutionTime(time.Now(), fmt.Sprintf(debugStr, e.userID, e.feedID, e.categoryID, e.status, e.order, e.direction, e.offset, e.limit)) + + query := ` + SELECT + e.id + FROM entries e + LEFT JOIN feeds f ON f.id=e.feed_id + WHERE %s %s + ` + + args, conditions := e.buildCondition() + query = fmt.Sprintf(query, conditions, e.buildSorting()) + // log.Println(query) + + rows, err := e.store.db.Query(query, args...) + if err != nil { + return nil, fmt.Errorf("unable to get entries: %v", err) + } + defer rows.Close() + + var entryIDs []int64 + for rows.Next() { + var entryID int64 + + err := rows.Scan(&entryID) + if err != nil { + return nil, fmt.Errorf("unable to fetch entry row: %v", err) + } + + entryIDs = append(entryIDs, entryID) + } + + return entryIDs, nil +} + func (e *EntryQueryBuilder) buildCondition() ([]interface{}, string) { args := []interface{}{e.userID} conditions := []string{"e.user_id = $1"} @@ -214,6 +275,16 @@ func (e *EntryQueryBuilder) buildCondition() ([]interface{}, string) { args = append(args, e.entryID) } + if e.greaterThanEntryID != 0 { + conditions = append(conditions, fmt.Sprintf("e.id > $%d", len(args)+1)) + args = append(args, e.greaterThanEntryID) + } + + if e.entryIDs != nil { + conditions = append(conditions, fmt.Sprintf("e.id=ANY($%d)", len(args)+1)) + args = append(args, pq.Array(e.entryIDs)) + } + if e.status != "" { conditions = append(conditions, fmt.Sprintf("e.status=$%d", len(args)+1)) args = append(args, e.status) @@ -224,6 +295,11 @@ func (e *EntryQueryBuilder) buildCondition() ([]interface{}, string) { args = append(args, e.notStatus) } + if e.before != nil { + conditions = append(conditions, fmt.Sprintf("e.published_at < $%d", len(args)+1)) + args = append(args, e.before) + } + return args, strings.Join(conditions, " AND ") } diff --git a/storage/icon.go b/storage/icon.go index e021d29..48b5f46 100644 --- a/storage/icon.go +++ b/storage/icon.go @@ -101,6 +101,37 @@ func (s *Storage) CreateFeedIcon(feed *model.Feed, icon *model.Icon) error { return nil } +// Icons returns all icons tht belongs to a user. +func (s *Storage) Icons(userID int64) (model.Icons, error) { + defer helper.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:Icons] userID=%d", userID)) + query := ` + SELECT + icons.id, icons.hash, icons.mime_type, icons.content + FROM icons + LEFT JOIN feed_icons ON feed_icons.icon_id=icons.id + LEFT JOIN feeds ON feeds.id=feed_icons.feed_id + WHERE feeds.user_id=$1 + ` + + rows, err := s.db.Query(query, userID) + if err != nil { + return nil, fmt.Errorf("unable to fetch icons: %v", err) + } + defer rows.Close() + + var icons model.Icons + for rows.Next() { + var icon model.Icon + err := rows.Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content) + if err != nil { + return nil, fmt.Errorf("unable to fetch icons row: %v", err) + } + icons = append(icons, &icon) + } + + return icons, nil +} + func normalizeMimeType(mimeType string) string { mimeType = strings.ToLower(mimeType) switch mimeType { diff --git a/storage/integration.go b/storage/integration.go index 07b67a8..1c461b9 100644 --- a/storage/integration.go +++ b/storage/integration.go @@ -11,6 +11,28 @@ import ( "github.com/miniflux/miniflux2/model" ) +// UserByFeverToken returns a user by using the Fever API token. +func (s *Storage) UserByFeverToken(token string) (*model.User, error) { + query := ` + SELECT + users.id, users.is_admin, users.timezone + FROM users + LEFT JOIN integrations ON integrations.user_id=users.id + WHERE integrations.fever_enabled='t' AND integrations.fever_token=$1 + ` + + var user model.User + err := s.db.QueryRow(query, token).Scan(&user.ID, &user.IsAdmin, &user.Timezone) + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + return nil, fmt.Errorf("unable to fetch user: %v", err) + } + + return &user, nil +} + // Integration returns user integration settings. func (s *Storage) Integration(userID int64) (*model.Integration, error) { query := `SELECT @@ -21,7 +43,11 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { pinboard_mark_as_unread, instapaper_enabled, instapaper_username, - instapaper_password + instapaper_password, + fever_enabled, + fever_username, + fever_password, + fever_token FROM integrations WHERE user_id=$1 ` @@ -35,6 +61,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { &integration.InstapaperEnabled, &integration.InstapaperUsername, &integration.InstapaperPassword, + &integration.FeverEnabled, + &integration.FeverUsername, + &integration.FeverPassword, + &integration.FeverToken, ) switch { case err == sql.ErrNoRows: @@ -56,8 +86,12 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { pinboard_mark_as_unread=$4, instapaper_enabled=$5, instapaper_username=$6, - instapaper_password=$7 - WHERE user_id=$8 + instapaper_password=$7, + fever_enabled=$8, + fever_username=$9, + fever_password=$10, + fever_token=$11 + WHERE user_id=$12 ` _, err := s.db.Exec( query, @@ -68,6 +102,10 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { integration.InstapaperEnabled, integration.InstapaperUsername, integration.InstapaperPassword, + integration.FeverEnabled, + integration.FeverUsername, + integration.FeverPassword, + integration.FeverToken, integration.UserID, ) -- cgit v1.2.3