aboutsummaryrefslogtreecommitdiffhomepage
path: root/storage
diff options
context:
space:
mode:
authorGravatar Frédéric Guillot <fred@miniflux.net>2017-12-03 17:44:27 -0800
committerGravatar Frédéric Guillot <fred@miniflux.net>2017-12-03 17:44:27 -0800
commitbc20e0884b3ca051ae77e1bb6e2de11419d36d4d (patch)
tree993ff7aad2b98d5fe71b2decde7b36c4dd99d184 /storage
parentae62e543d3a1173cd39f1910cb67c95a56a7a6a4 (diff)
Add Fever API
Diffstat (limited to 'storage')
-rw-r--r--storage/entry_query_builder.go100
-rw-r--r--storage/icon.go31
-rw-r--r--storage/integration.go44
3 files changed, 160 insertions, 15 deletions
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,
)