From d3883126bfca47f1af57a6388ff7b00c997c924f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Tue, 29 Oct 2019 22:48:07 -0700 Subject: [PATCH] Improve storage module --- model/feed.go | 4 +- storage/category.go | 104 ++++++++++-------- storage/enclosure.go | 31 ++++-- storage/entry.go | 137 +++++++++++++++--------- storage/entry_query_builder.go | 8 -- storage/feed.go | 185 +++++++++++++++++++++------------ storage/icon.go | 43 ++++---- storage/integration.go | 57 +++++----- storage/job.go | 24 +++-- storage/session.go | 43 +++++--- storage/timezone.go | 4 +- storage/user.go | 98 ++++++++++------- storage/user_session.go | 84 ++++++++++----- 13 files changed, 505 insertions(+), 317 deletions(-) diff --git a/model/feed.go b/model/feed.go index 0e25d7b4..9d0ab279 100644 --- a/model/feed.go +++ b/model/feed.go @@ -33,8 +33,8 @@ type Feed struct { Category *Category `json:"category,omitempty"` Entries Entries `json:"entries,omitempty"` Icon *FeedIcon `json:"icon"` - UnreadCount int `json:"unread_count"` - ReadCount int `json:"read_count"` + UnreadCount int + ReadCount int } func (f *Feed) String() string { diff --git a/storage/category.go b/storage/category.go index 72ba24b9..07206c08 100644 --- a/storage/category.go +++ b/storage/category.go @@ -14,18 +14,18 @@ import ( // AnotherCategoryExists checks if another category exists with the same title. func (s *Storage) AnotherCategoryExists(userID, categoryID int64, title string) bool { - var result int - query := `SELECT count(*) as c FROM categories WHERE user_id=$1 AND id != $2 AND title=$3` + var result bool + query := `SELECT true FROM categories WHERE user_id=$1 AND id != $2 AND title=$3` s.db.QueryRow(query, userID, categoryID, title).Scan(&result) - return result >= 1 + return result } // CategoryExists checks if the given category exists into the database. func (s *Storage) CategoryExists(userID, categoryID int64) bool { - var result int - query := `SELECT count(*) as c FROM categories WHERE user_id=$1 AND id=$2` + var result bool + query := `SELECT true FROM categories WHERE user_id=$1 AND id=$2` s.db.QueryRow(query, userID, categoryID).Scan(&result) - return result >= 1 + return result } // Category returns a category from the database. @@ -34,28 +34,32 @@ func (s *Storage) Category(userID, categoryID int64) (*model.Category, error) { query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 AND id=$2` err := s.db.QueryRow(query, userID, categoryID).Scan(&category.ID, &category.UserID, &category.Title) - if err == sql.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("unable to fetch category: %v", err) - } - return &category, nil + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + return nil, fmt.Errorf(`store: unable to fetch category: %v`, err) + default: + return &category, nil + } } // FirstCategory returns the first category for the given user. func (s *Storage) FirstCategory(userID int64) (*model.Category, error) { - var category model.Category - query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC LIMIT 1` - err := s.db.QueryRow(query, userID).Scan(&category.ID, &category.UserID, &category.Title) - if err == sql.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("unable to fetch category: %v", err) - } - return &category, nil + var category model.Category + err := s.db.QueryRow(query, userID).Scan(&category.ID, &category.UserID, &category.Title) + + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + return nil, fmt.Errorf(`store: unable to fetch category: %v`, err) + default: + return &category, nil + } } // CategoryByTitle finds a category by the title. @@ -64,13 +68,15 @@ func (s *Storage) CategoryByTitle(userID int64, title string) (*model.Category, query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 AND title=$2` err := s.db.QueryRow(query, userID, title).Scan(&category.ID, &category.UserID, &category.Title) - if err == sql.ErrNoRows { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("Unable to fetch category: %v", err) - } - return &category, nil + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + return nil, fmt.Errorf(`store: unable to fetch category: %v`, err) + default: + return &category, nil + } } // Categories returns all categories that belongs to the given user. @@ -78,7 +84,7 @@ func (s *Storage) Categories(userID int64) (model.Categories, error) { query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC` rows, err := s.db.Query(query, userID) if err != nil { - return nil, fmt.Errorf("Unable to fetch categories: %v", err) + return nil, fmt.Errorf(`store: unable to fetch categories: %v`, err) } defer rows.Close() @@ -86,7 +92,7 @@ func (s *Storage) Categories(userID int64) (model.Categories, error) { for rows.Next() { var category model.Category if err := rows.Scan(&category.ID, &category.UserID, &category.Title); err != nil { - return nil, fmt.Errorf("Unable to fetch categories row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch category row: %v`, err) } categories = append(categories, &category) @@ -97,15 +103,21 @@ func (s *Storage) Categories(userID int64) (model.Categories, error) { // CategoriesWithFeedCount returns all categories with the number of feeds. func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error) { - query := `SELECT - c.id, c.user_id, c.title, - (SELECT count(*) FROM feeds WHERE feeds.category_id=c.id) AS count - FROM categories c WHERE user_id=$1 - ORDER BY c.title ASC` + query := ` + SELECT + c.id, + c.user_id, + c.title, + (SELECT count(*) FROM feeds WHERE feeds.category_id=c.id) AS count + FROM categories c + WHERE + user_id=$1 + ORDER BY c.title ASC + ` rows, err := s.db.Query(query, userID) if err != nil { - return nil, fmt.Errorf("Unable to fetch categories: %v", err) + return nil, fmt.Errorf(`store: unable to fetch categories: %v`, err) } defer rows.Close() @@ -113,7 +125,7 @@ func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error for rows.Next() { var category model.Category if err := rows.Scan(&category.ID, &category.UserID, &category.Title, &category.FeedCount); err != nil { - return nil, fmt.Errorf("Unable to fetch categories row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch category row: %v`, err) } categories = append(categories, &category) @@ -126,10 +138,11 @@ func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error func (s *Storage) CreateCategory(category *model.Category) error { query := ` INSERT INTO categories - (user_id, title) + (user_id, title) VALUES - ($1, $2) - RETURNING id + ($1, $2) + RETURNING + id ` err := s.db.QueryRow( query, @@ -138,7 +151,7 @@ func (s *Storage) CreateCategory(category *model.Category) error { ).Scan(&category.ID) if err != nil { - return fmt.Errorf("Unable to create category: %v", err) + return fmt.Errorf(`store: unable to create category: %v`, err) } return nil @@ -155,7 +168,7 @@ func (s *Storage) UpdateCategory(category *model.Category) error { ) if err != nil { - return fmt.Errorf("Unable to update category: %v", err) + return fmt.Errorf(`store: unable to update category: %v`, err) } return nil @@ -163,18 +176,19 @@ func (s *Storage) UpdateCategory(category *model.Category) error { // RemoveCategory deletes a category. func (s *Storage) RemoveCategory(userID, categoryID int64) error { - result, err := s.db.Exec("DELETE FROM categories WHERE id = $1 AND user_id = $2", categoryID, userID) + query := `DELETE FROM categories WHERE id = $1 AND user_id = $2` + result, err := s.db.Exec(query, categoryID, userID) if err != nil { - return fmt.Errorf("Unable to remove this category: %v", err) + return fmt.Errorf(`store: unable to remove this category: %v`, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("Unable to remove this category: %v", err) + return fmt.Errorf(`store: unable to remove this category: %v`, err) } if count == 0 { - return errors.New("no category has been removed") + return errors.New(`store: no category has been removed`) } return nil diff --git a/storage/enclosure.go b/storage/enclosure.go index 604dc028..6684c781 100644 --- a/storage/enclosure.go +++ b/storage/enclosure.go @@ -12,14 +12,24 @@ import ( // GetEnclosures returns all attachments for the given entry. func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) { - query := `SELECT - id, user_id, entry_id, url, size, mime_type - FROM enclosures - WHERE entry_id = $1 ORDER BY id ASC` + query := ` + SELECT + id, + user_id, + entry_id, + url, + size, + mime_type + FROM + enclosures + WHERE + entry_id = $1 + ORDER BY id ASC + ` rows, err := s.db.Query(query, entryID) if err != nil { - return nil, fmt.Errorf("unable to get enclosures: %v", err) + return nil, fmt.Errorf(`store: unable to fetch enclosures: %v`, err) } defer rows.Close() @@ -36,7 +46,7 @@ func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) { ) if err != nil { - return nil, fmt.Errorf("unable to fetch enclosure row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch enclosure row: %v`, err) } enclosures = append(enclosures, &enclosure) @@ -49,10 +59,11 @@ func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) { func (s *Storage) CreateEnclosure(enclosure *model.Enclosure) error { query := ` INSERT INTO enclosures - (url, size, mime_type, entry_id, user_id) + (url, size, mime_type, entry_id, user_id) VALUES - ($1, $2, $3, $4, $5) - RETURNING id + ($1, $2, $3, $4, $5) + RETURNING + id ` err := s.db.QueryRow( query, @@ -64,7 +75,7 @@ func (s *Storage) CreateEnclosure(enclosure *model.Enclosure) error { ).Scan(&enclosure.ID) if err != nil { - return fmt.Errorf("unable to create enclosure %q: %v", enclosure.URL, err) + return fmt.Errorf(`store: unable to create enclosure %q: %v`, enclosure.URL, err) } return nil diff --git a/storage/entry.go b/storage/entry.go index f36bcbe7..9a717ebb 100644 --- a/storage/entry.go +++ b/storage/entry.go @@ -22,7 +22,7 @@ func (s *Storage) CountUnreadEntries(userID int64) int { n, err := builder.CountEntries() if err != nil { - logger.Error("unable to count unread entries for user #%d: %v", userID, err) + logger.Error(`store: unable to count unread entries for user #%d: %v`, userID, err) return 0 } @@ -41,21 +41,32 @@ func (s *Storage) UpdateEntryContent(entry *model.Entry) error { return err } - _, err = tx.Exec(`UPDATE entries SET content=$1 WHERE id=$2 AND user_id=$3`, entry.Content, entry.ID, entry.UserID) + query := ` + UPDATE + entries + SET + content=$1 + WHERE + id=$2 AND user_id=$3 + ` + _, err = tx.Exec(query, entry.Content, entry.ID, entry.UserID) if err != nil { tx.Rollback() - return fmt.Errorf(`unable to update content of entry #%d: %v`, entry.ID, err) + return fmt.Errorf(`store: unable to update content of entry #%d: %v`, entry.ID, err) } - query := ` - UPDATE entries - SET document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B') - WHERE id=$1 AND user_id=$2 + query = ` + UPDATE + entries + SET + document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B') + WHERE + id=$1 AND user_id=$2 ` _, err = tx.Exec(query, entry.ID, entry.UserID) if err != nil { tx.Rollback() - return fmt.Errorf(`unable to update content of entry #%d: %v`, entry.ID, err) + return fmt.Errorf(`store: unable to update content of entry #%d: %v`, entry.ID, err) } return tx.Commit() @@ -65,10 +76,11 @@ func (s *Storage) UpdateEntryContent(entry *model.Entry) error { func (s *Storage) createEntry(entry *model.Entry) error { query := ` INSERT INTO entries - (title, hash, url, comments_url, published_at, content, author, user_id, feed_id, document_vectors) + (title, hash, url, comments_url, published_at, content, author, user_id, feed_id, document_vectors) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, setweight(to_tsvector(substring(coalesce($1, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce($6, '') for 1000000)), 'B')) - RETURNING id, status + ($1, $2, $3, $4, $5, $6, $7, $8, $9, setweight(to_tsvector(substring(coalesce($1, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce($6, '') for 1000000)), 'B')) + RETURNING + id, status ` err := s.db.QueryRow( query, @@ -84,7 +96,7 @@ func (s *Storage) createEntry(entry *model.Entry) error { ).Scan(&entry.ID, &entry.Status) if err != nil { - return fmt.Errorf("Unable to create entry %q (feed #%d): %v", entry.URL, entry.FeedID, err) + return fmt.Errorf(`store: unable to create entry %q (feed #%d): %v`, entry.URL, entry.FeedID, err) } for i := 0; i < len(entry.Enclosures); i++ { @@ -104,11 +116,19 @@ func (s *Storage) createEntry(entry *model.Entry) error { // it default to time.Now() which could change the order of items on the history page. func (s *Storage) updateEntry(entry *model.Entry) error { query := ` - UPDATE entries SET - title=$1, url=$2, comments_url=$3, content=$4, author=$5, - document_vectors = setweight(to_tsvector(substring(coalesce($1, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce($4, '') for 1000000)), 'B') - WHERE user_id=$6 AND feed_id=$7 AND hash=$8 - RETURNING id + UPDATE + entries + SET + title=$1, + url=$2, + comments_url=$3, + content=$4, + author=$5, + document_vectors = setweight(to_tsvector(substring(coalesce($1, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce($4, '') for 1000000)), 'B') + WHERE + user_id=$6 AND feed_id=$7 AND hash=$8 + RETURNING + id ` err := s.db.QueryRow( query, @@ -123,7 +143,7 @@ func (s *Storage) updateEntry(entry *model.Entry) error { ).Scan(&entry.ID) if err != nil { - return fmt.Errorf(`unable to update entry %q: %v`, entry.URL, err) + return fmt.Errorf(`store: unable to update entry %q: %v`, entry.URL, err) } for _, enclosure := range entry.Enclosures { @@ -145,12 +165,15 @@ func (s *Storage) entryExists(entry *model.Entry) bool { // cleanupEntries deletes from the database entries marked as "removed" and not visible anymore in the feed. func (s *Storage) cleanupEntries(feedID int64, entryHashes []string) error { query := ` - DELETE FROM entries - WHERE feed_id=$1 AND - id IN (SELECT id FROM entries WHERE feed_id=$2 AND status=$3 AND NOT (hash=ANY($4))) + DELETE FROM + entries + WHERE + feed_id=$1 + AND + id IN (SELECT id FROM entries WHERE feed_id=$2 AND status=$3 AND NOT (hash=ANY($4))) ` if _, err := s.db.Exec(query, feedID, feedID, model.EntryStatusRemoved, pq.Array(entryHashes)); err != nil { - return fmt.Errorf("unable to cleanup entries: %v", err) + return fmt.Errorf(`store: unable to cleanup entries: %v`, err) } return nil @@ -179,7 +202,7 @@ func (s *Storage) UpdateEntries(userID, feedID int64, entries model.Entries, upd } if err := s.cleanupEntries(feedID, entryHashes); err != nil { - logger.Error("[Storage:CleanupEntries] feed #%d: %v", feedID, err) + logger.Error(`store: feed #%d: %v`, feedID, err) } return nil @@ -190,12 +213,17 @@ func (s *Storage) ArchiveEntries(days int) error { if days < 0 { return nil } - query := fmt.Sprintf(` - UPDATE entries SET status='removed' - WHERE id=ANY(SELECT id FROM entries WHERE status='read' AND starred is false AND published_at < now () - '%d days'::interval LIMIT 5000) - `, days) - if _, err := s.db.Exec(query); err != nil { - return fmt.Errorf("unable to archive read entries: %v", err) + + query := ` + UPDATE + entries + SET + status='removed' + WHERE + id=ANY(SELECT id FROM entries WHERE status='read' AND starred is false AND published_at < now () - '%d days'::interval LIMIT 5000) + ` + if _, err := s.db.Exec(fmt.Sprintf(query, days)); err != nil { + return fmt.Errorf(`store: unable to archive read entries: %v`, err) } return nil @@ -206,16 +234,16 @@ func (s *Storage) SetEntriesStatus(userID int64, entryIDs []int64, status string query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND id=ANY($3)` result, err := s.db.Exec(query, status, userID, pq.Array(entryIDs)) if err != nil { - return fmt.Errorf("unable to update entries statuses %v: %v", entryIDs, err) + return fmt.Errorf(`store: unable to update entries statuses %v: %v`, entryIDs, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("unable to update these entries %v: %v", entryIDs, err) + return fmt.Errorf(`store: unable to update these entries %v: %v`, entryIDs, err) } if count == 0 { - return errors.New("nothing has been updated") + return errors.New(`store: nothing has been updated`) } return nil @@ -226,16 +254,16 @@ func (s *Storage) ToggleBookmark(userID int64, entryID int64) error { query := `UPDATE entries SET starred = NOT starred WHERE user_id=$1 AND id=$2` result, err := s.db.Exec(query, userID, entryID) if err != nil { - return fmt.Errorf("unable to toggle bookmark flag for entry #%d: %v", entryID, err) + return fmt.Errorf(`store: unable to toggle bookmark flag for entry #%d: %v`, entryID, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("unable to toogle bookmark flag for entry #%d: %v", entryID, err) + return fmt.Errorf(`store: unable to toogle bookmark flag for entry #%d: %v`, entryID, err) } if count == 0 { - return errors.New("nothing has been updated") + return errors.New(`store: nothing has been updated`) } return nil @@ -246,7 +274,7 @@ func (s *Storage) FlushHistory(userID int64) error { query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND status=$3 AND starred='f'` _, err := s.db.Exec(query, model.EntryStatusRemoved, userID, model.EntryStatusRead) if err != nil { - return fmt.Errorf("unable to flush history: %v", err) + return fmt.Errorf(`store: unable to flush history: %v`, err) } return nil @@ -257,7 +285,7 @@ func (s *Storage) MarkAllAsRead(userID int64) error { query := `UPDATE entries SET status=$1 WHERE user_id=$2 AND status=$3` result, err := s.db.Exec(query, model.EntryStatusRead, userID, model.EntryStatusUnread) if err != nil { - return fmt.Errorf("unable to mark all entries as read: %v", err) + return fmt.Errorf(`store: unable to mark all entries as read: %v`, err) } count, _ := result.RowsAffected() @@ -269,14 +297,16 @@ func (s *Storage) MarkAllAsRead(userID int64) error { // MarkFeedAsRead updates all feed entries to the read status. func (s *Storage) MarkFeedAsRead(userID, feedID int64, before time.Time) error { query := ` - UPDATE entries - SET status=$1 - WHERE user_id=$2 AND feed_id=$3 AND status=$4 AND published_at < $5 + UPDATE + entries + SET + status=$1 + WHERE + user_id=$2 AND feed_id=$3 AND status=$4 AND published_at < $5 ` - result, err := s.db.Exec(query, model.EntryStatusRead, userID, feedID, model.EntryStatusUnread, before) if err != nil { - return fmt.Errorf("unable to mark feed entries as read: %v", err) + return fmt.Errorf(`store: unable to mark feed entries as read: %v`, err) } count, _ := result.RowsAffected() @@ -288,15 +318,22 @@ func (s *Storage) MarkFeedAsRead(userID, feedID int64, before time.Time) error { // MarkCategoryAsRead updates all category entries to the read status. func (s *Storage) MarkCategoryAsRead(userID, categoryID int64, before time.Time) error { query := ` - UPDATE entries - SET status=$1 + UPDATE + entries + SET + status=$1 WHERE - user_id=$2 AND status=$3 AND published_at < $4 AND feed_id IN (SELECT id FROM feeds WHERE user_id=$2 AND category_id=$5) + user_id=$2 + AND + status=$3 + AND + published_at < $4 + AND + feed_id IN (SELECT id FROM feeds WHERE user_id=$2 AND category_id=$5) ` - result, err := s.db.Exec(query, model.EntryStatusRead, userID, model.EntryStatusUnread, before, categoryID) if err != nil { - return fmt.Errorf("unable to mark category entries as read: %v", err) + return fmt.Errorf(`store: unable to mark category entries as read: %v`, err) } count, _ := result.RowsAffected() @@ -307,8 +344,8 @@ func (s *Storage) MarkCategoryAsRead(userID, categoryID int64, before time.Time) // EntryURLExists returns true if an entry with this URL already exists. func (s *Storage) EntryURLExists(feedID int64, entryURL string) bool { - var result int - query := `SELECT count(*) as c FROM entries WHERE feed_id=$1 AND url=$2` + var result bool + query := `SELECT true FROM entries WHERE feed_id=$1 AND url=$2` s.db.QueryRow(query, feedID, entryURL).Scan(&result) - return result >= 1 + return result } diff --git a/storage/entry_query_builder.go b/storage/entry_query_builder.go index c4f6e437..66f1d649 100644 --- a/storage/entry_query_builder.go +++ b/storage/entry_query_builder.go @@ -12,7 +12,6 @@ import ( "github.com/lib/pq" "miniflux.app/model" - "miniflux.app/timer" "miniflux.app/timezone" ) @@ -158,8 +157,6 @@ func (e *EntryQueryBuilder) CountEntries() (count int, err error) { query := `SELECT count(*) FROM entries e LEFT JOIN feeds f ON f.id=e.feed_id WHERE %s` condition := e.buildCondition() - defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[EntryQueryBuilder:CountEntries] %s, args=%v", condition, e.args)) - err = e.store.db.QueryRow(fmt.Sprintf(query, condition), e.args...).Scan(&count) if err != nil { return 0, fmt.Errorf("unable to count entries: %v", err) @@ -210,8 +207,6 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) { sorting := e.buildSorting() query = fmt.Sprintf(query, condition, sorting) - defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[EntryQueryBuilder:GetEntries] %s, args=%v, sorting=%s", condition, e.args, sorting)) - rows, err := e.store.db.Query(query, e.args...) if err != nil { return nil, fmt.Errorf("unable to get entries: %v", err) @@ -285,9 +280,6 @@ func (e *EntryQueryBuilder) GetEntryIDs() ([]int64, error) { condition := e.buildCondition() query = fmt.Sprintf(query, condition, e.buildSorting()) - // log.Println(query) - - defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[EntryQueryBuilder:GetEntryIDs] condition=%s, args=%v", condition, e.args)) rows, err := e.store.db.Query(query, e.args...) if err != nil { diff --git a/storage/feed.go b/storage/feed.go index 45ca9bea..f6ad021f 100644 --- a/storage/feed.go +++ b/storage/feed.go @@ -15,18 +15,18 @@ import ( // FeedExists checks if the given feed exists. func (s *Storage) FeedExists(userID, feedID int64) bool { - var result int - query := `SELECT count(*) as c FROM feeds WHERE user_id=$1 AND id=$2` + var result bool + query := `SELECT true FROM feeds WHERE user_id=$1 AND id=$2` s.db.QueryRow(query, userID, feedID).Scan(&result) - return result >= 1 + return result } // FeedURLExists checks if feed URL already exists. func (s *Storage) FeedURLExists(userID int64, feedURL string) bool { - var result int - query := `SELECT count(*) as c FROM feeds WHERE user_id=$1 AND feed_url=$2` + var result bool + query := `SELECT true FROM feeds WHERE user_id=$1 AND feed_url=$2` s.db.QueryRow(query, userID, feedURL).Scan(&result) - return result >= 1 + return result } // CountFeeds returns the number of feeds that belongs to the given user. @@ -42,8 +42,9 @@ func (s *Storage) CountFeeds(userID int64) int { // CountErrorFeeds returns the number of feeds with parse errors that belong to the given user. func (s *Storage) CountErrorFeeds(userID int64) int { + query := `SELECT count(*) FROM feeds WHERE user_id=$1 AND parsing_error_count>=$2` var result int - err := s.db.QueryRow(`SELECT count(*) FROM feeds WHERE user_id=$1 AND parsing_error_count>=$2`, userID, maxParsingError).Scan(&result) + err := s.db.QueryRow(query, userID, maxParsingError).Scan(&result) if err != nil { return 0 } @@ -54,25 +55,40 @@ func (s *Storage) CountErrorFeeds(userID int64) int { // Feeds returns all feeds of the given user. func (s *Storage) Feeds(userID int64) (model.Feeds, error) { feeds := make(model.Feeds, 0) - query := `SELECT - f.id, f.feed_url, f.site_url, f.title, f.etag_header, f.last_modified_header, - f.user_id, f.checked_at at time zone u.timezone, - f.parsing_error_count, f.parsing_error_msg, - f.scraper_rules, f.rewrite_rules, f.crawler, f.user_agent, - f.username, f.password, f.disabled, - f.category_id, c.title as category_title, - fi.icon_id, - u.timezone + query := ` + SELECT + f.id, + f.feed_url, + f.site_url, + f.title, + f.etag_header, + f.last_modified_header, + f.user_id, + f.checked_at at time zone u.timezone, + f.parsing_error_count, + f.parsing_error_msg, + f.scraper_rules, + f.rewrite_rules, + f.crawler, + f.user_agent, + f.username, + f.password, + f.disabled, + f.category_id, + c.title as category_title, + fi.icon_id, + u.timezone FROM feeds f LEFT JOIN categories c ON c.id=f.category_id LEFT JOIN feed_icons fi ON fi.feed_id=f.id LEFT JOIN users u ON u.id=f.user_id - WHERE f.user_id=$1 - ORDER BY f.parsing_error_count DESC, lower(f.title) ASC` - + WHERE + f.user_id=$1 + ORDER BY f.parsing_error_count DESC, lower(f.title) ASC + ` rows, err := s.db.Query(query, userID) if err != nil { - return nil, fmt.Errorf("unable to fetch feeds: %v", err) + return nil, fmt.Errorf(`store: unable to fetch feeds: %v`, err) } defer rows.Close() @@ -107,7 +123,7 @@ func (s *Storage) Feeds(userID int64) (model.Feeds, error) { ) if err != nil { - return nil, fmt.Errorf("unable to fetch feeds row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch feeds row: %v`, err) } if iconID != nil { @@ -124,27 +140,35 @@ func (s *Storage) Feeds(userID int64) (model.Feeds, error) { // FeedsWithCounters returns all feeds of the given user with counters of read and unread entries. func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { feeds := make(model.Feeds, 0) - query := `SELECT - f.id, f.feed_url, f.site_url, f.title, f.etag_header, f.last_modified_header, - f.user_id, f.checked_at at time zone u.timezone, - f.parsing_error_count, f.parsing_error_msg, - f.scraper_rules, f.rewrite_rules, f.crawler, f.user_agent, - f.username, f.password, f.disabled, - f.category_id, c.title as category_title, - fi.icon_id, - u.timezone, - (SELECT count(*) FROM entries WHERE entries.feed_id=f.id AND status='unread') as unread_count, - (SELECT count(*) FROM entries WHERE entries.feed_id=f.id AND status='read') as read_count + query := ` + SELECT + f.id, + f.feed_url, + f.site_url, + f.title, + f.etag_header, + f.last_modified_header, + f.user_id, + f.checked_at at time zone u.timezone, + f.parsing_error_count, f.parsing_error_msg, + f.scraper_rules, f.rewrite_rules, f.crawler, f.user_agent, + f.username, f.password, f.disabled, + f.category_id, c.title as category_title, + fi.icon_id, + u.timezone, + (SELECT count(*) FROM entries WHERE entries.feed_id=f.id AND status='unread') as unread_count, + (SELECT count(*) FROM entries WHERE entries.feed_id=f.id AND status='read') as read_count FROM feeds f LEFT JOIN categories c ON c.id=f.category_id LEFT JOIN feed_icons fi ON fi.feed_id=f.id LEFT JOIN users u ON u.id=f.user_id - WHERE f.user_id=$1 - ORDER BY f.parsing_error_count DESC, unread_count DESC, lower(f.title) ASC` - + WHERE + f.user_id=$1 + ORDER BY f.parsing_error_count DESC, unread_count DESC, lower(f.title) ASC + ` rows, err := s.db.Query(query, userID) if err != nil { - return nil, fmt.Errorf("unable to fetch feeds: %v", err) + return nil, fmt.Errorf(`store: unable to fetch feeds: %v`, err) } defer rows.Close() @@ -181,7 +205,7 @@ func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { ) if err != nil { - return nil, fmt.Errorf("unable to fetch feeds row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch feeds row: %v`, err) } if iconID != nil { @@ -204,19 +228,33 @@ func (s *Storage) FeedByID(userID, feedID int64) (*model.Feed, error) { query := ` SELECT - f.id, f.feed_url, f.site_url, f.title, f.etag_header, f.last_modified_header, - f.user_id, f.checked_at at time zone u.timezone, - f.parsing_error_count, f.parsing_error_msg, - f.scraper_rules, f.rewrite_rules, f.crawler, f.user_agent, - f.username, f.password, f.disabled, - f.category_id, c.title as category_title, - fi.icon_id, - u.timezone + f.id, + f.feed_url, + f.site_url, + f.title, + f.etag_header, + f.last_modified_header, + f.user_id, f.checked_at at time zone u.timezone, + f.parsing_error_count, + f.parsing_error_msg, + f.scraper_rules, + f.rewrite_rules, + f.crawler, + f.user_agent, + f.username, + f.password, + f.disabled, + f.category_id, + c.title as category_title, + fi.icon_id, + u.timezone FROM feeds f LEFT JOIN categories c ON c.id=f.category_id LEFT JOIN feed_icons fi ON fi.feed_id=f.id LEFT JOIN users u ON u.id=f.user_id - WHERE f.user_id=$1 AND f.id=$2` + WHERE + f.user_id=$1 AND f.id=$2 + ` err := s.db.QueryRow(query, userID, feedID).Scan( &feed.ID, @@ -246,7 +284,7 @@ func (s *Storage) FeedByID(userID, feedID int64) (*model.Feed, error) { case err == sql.ErrNoRows: return nil, nil case err != nil: - return nil, fmt.Errorf("unable to fetch feed #%d: %v", feedID, err) + return nil, fmt.Errorf(`store: unable to fetch feed #%d: %v`, feedID, err) } if iconID != nil { @@ -260,14 +298,25 @@ func (s *Storage) FeedByID(userID, feedID int64) (*model.Feed, error) { // CreateFeed creates a new feed. func (s *Storage) CreateFeed(feed *model.Feed) error { sql := ` - INSERT INTO feeds - (feed_url, site_url, title, category_id, user_id, etag_header, - last_modified_header, crawler, user_agent, username, password, - disabled) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) - RETURNING id + INSERT INTO feeds ( + feed_url, + site_url, + title, + category_id, + user_id, + etag_header, + last_modified_header, + crawler, + user_agent, + username, + password, + disabled + ) + VALUES + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + RETURNING + id ` - err := s.db.QueryRow( sql, feed.FeedURL, @@ -284,7 +333,7 @@ func (s *Storage) CreateFeed(feed *model.Feed) error { feed.Disabled, ).Scan(&feed.ID) if err != nil { - return fmt.Errorf("unable to create feed %q: %v", feed.FeedURL, err) + return fmt.Errorf(`store: unable to create feed %q: %v`, feed.FeedURL, err) } for i := 0; i < len(feed.Entries); i++ { @@ -305,7 +354,9 @@ func (s *Storage) CreateFeed(feed *model.Feed) error { // UpdateFeed updates an existing feed. func (s *Storage) UpdateFeed(feed *model.Feed) (err error) { query := ` - UPDATE feeds SET + UPDATE + feeds + SET feed_url=$1, site_url=$2, title=$3, @@ -325,7 +376,6 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) { WHERE id=$17 AND user_id=$18 ` - _, err = s.db.Exec(query, feed.FeedURL, feed.SiteURL, @@ -348,7 +398,7 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) { ) if err != nil { - return fmt.Errorf("unable to update feed #%d (%s): %v", feed.ID, feed.FeedURL, err) + return fmt.Errorf(`store: unable to update feed #%d (%s): %v`, feed.ID, feed.FeedURL, err) } return nil @@ -357,13 +407,15 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) { // UpdateFeedError updates feed errors. func (s *Storage) UpdateFeedError(feed *model.Feed) (err error) { query := ` - UPDATE feeds + UPDATE + feeds SET parsing_error_msg=$1, parsing_error_count=$2, checked_at=$3 - WHERE id=$4 AND user_id=$5` - + WHERE + id=$4 AND user_id=$5 + ` _, err = s.db.Exec(query, feed.ParsingErrorMsg, feed.ParsingErrorCount, @@ -373,7 +425,7 @@ func (s *Storage) UpdateFeedError(feed *model.Feed) (err error) { ) if err != nil { - return fmt.Errorf("unable to update feed error #%d (%s): %v", feed.ID, feed.FeedURL, err) + return fmt.Errorf(`store: unable to update feed error #%d (%s): %v`, feed.ID, feed.FeedURL, err) } return nil @@ -381,18 +433,19 @@ func (s *Storage) UpdateFeedError(feed *model.Feed) (err error) { // RemoveFeed removes a feed. func (s *Storage) RemoveFeed(userID, feedID int64) error { - result, err := s.db.Exec("DELETE FROM feeds WHERE id = $1 AND user_id = $2", feedID, userID) + query := `DELETE FROM feeds WHERE id = $1 AND user_id = $2` + result, err := s.db.Exec(query, feedID, userID) if err != nil { - return fmt.Errorf("unable to remove feed #%d: %v", feedID, err) + return fmt.Errorf(`store: unable to remove feed #%d: %v`, feedID, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("unable to remove feed #%d: %v", feedID, err) + return fmt.Errorf(`store: unable to remove feed #%d: %v`, feedID, err) } if count == 0 { - return errors.New("no feed has been removed") + return errors.New(`store: no feed has been removed`) } return nil diff --git a/storage/icon.go b/storage/icon.go index 7ad7d064..af7f323d 100644 --- a/storage/icon.go +++ b/storage/icon.go @@ -14,10 +14,10 @@ import ( // HasIcon checks if the given feed has an icon. func (s *Storage) HasIcon(feedID int64) bool { - var result int - query := `SELECT count(*) as c FROM feed_icons WHERE feed_id=$1` + var result bool + query := `SELECT true FROM feed_icons WHERE feed_id=$1` s.db.QueryRow(query, feedID).Scan(&result) - return result == 1 + return result } // IconByID returns an icon by the ID. @@ -38,18 +38,21 @@ func (s *Storage) IconByID(iconID int64) (*model.Icon, error) { func (s *Storage) IconByFeedID(userID, feedID int64) (*model.Icon, error) { query := ` SELECT - icons.id, icons.hash, icons.mime_type, icons.content + 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 AND feeds.id=$2 + WHERE + feeds.user_id=$1 AND feeds.id=$2 LIMIT 1 ` - var icon model.Icon err := s.db.QueryRow(query, userID, feedID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content) if err != nil { - return nil, fmt.Errorf("unable to fetch icon: %v", err) + return nil, fmt.Errorf(`store: unable to fetch icon: %v`, err) } return &icon, nil @@ -61,7 +64,7 @@ func (s *Storage) IconByHash(icon *model.Icon) error { if err == sql.ErrNoRows { return nil } else if err != nil { - return fmt.Errorf("Unable to fetch icon by hash: %v", err) + return fmt.Errorf(`store: unable to fetch icon by hash: %v`, err) } return nil @@ -71,10 +74,11 @@ func (s *Storage) IconByHash(icon *model.Icon) error { func (s *Storage) CreateIcon(icon *model.Icon) error { query := ` INSERT INTO icons - (hash, mime_type, content) + (hash, mime_type, content) VALUES - ($1, $2, $3) - RETURNING id + ($1, $2, $3) + RETURNING + id ` err := s.db.QueryRow( query, @@ -84,7 +88,7 @@ func (s *Storage) CreateIcon(icon *model.Icon) error { ).Scan(&icon.ID) if err != nil { - return fmt.Errorf("Unable to create icon: %v", err) + return fmt.Errorf(`store: unable to create icon: %v`, err) } return nil @@ -106,7 +110,7 @@ func (s *Storage) CreateFeedIcon(feedID int64, icon *model.Icon) error { _, err = s.db.Exec(`INSERT INTO feed_icons (feed_id, icon_id) VALUES ($1, $2)`, feedID, icon.ID) if err != nil { - return fmt.Errorf("unable to create feed icon: %v", err) + return fmt.Errorf(`store: unable to create feed icon: %v`, err) } return nil @@ -116,16 +120,19 @@ func (s *Storage) CreateFeedIcon(feedID int64, icon *model.Icon) error { func (s *Storage) Icons(userID int64) (model.Icons, error) { query := ` SELECT - icons.id, icons.hash, icons.mime_type, icons.content + 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 + 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) + return nil, fmt.Errorf(`store: unable to fetch icons: %v`, err) } defer rows.Close() @@ -134,7 +141,7 @@ func (s *Storage) Icons(userID int64) (model.Icons, error) { 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) + return nil, fmt.Errorf(`store: unable to fetch icons row: %v`, err) } icons = append(icons, &icon) } diff --git a/storage/integration.go b/storage/integration.go index 98d15e8a..e9653d3d 100644 --- a/storage/integration.go +++ b/storage/integration.go @@ -13,16 +13,10 @@ import ( // HasDuplicateFeverUsername checks if another user have the same fever username. func (s *Storage) HasDuplicateFeverUsername(userID int64, feverUsername string) bool { - query := ` - SELECT - count(*) as c - FROM integrations - WHERE user_id != $1 AND fever_username=$2 - ` - - var result int + query := `SELECT true FROM integrations WHERE user_id != $1 AND fever_username=$2` + var result bool s.db.QueryRow(query, userID, feverUsername).Scan(&result) - return result >= 1 + return result } // UserByFeverToken returns a user by using the Fever API token. @@ -32,7 +26,8 @@ func (s *Storage) UserByFeverToken(token string) (*model.User, error) { users.id, users.is_admin, users.timezone FROM users LEFT JOIN integrations ON integrations.user_id=users.id - WHERE integrations.fever_enabled='t' AND lower(integrations.fever_token)=lower($1) + WHERE + integrations.fever_enabled='t' AND lower(integrations.fever_token)=lower($1) ` var user model.User @@ -42,14 +37,15 @@ func (s *Storage) UserByFeverToken(token string) (*model.User, error) { return nil, nil case err != nil: return nil, fmt.Errorf("unable to fetch user: %v", err) + default: + return &user, nil } - - return &user, nil } // Integration returns user integration settings. func (s *Storage) Integration(userID int64) (*model.Integration, error) { - query := `SELECT + query := ` + SELECT user_id, pinboard_enabled, pinboard_token, @@ -74,8 +70,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { pocket_enabled, pocket_access_token, pocket_consumer_key - FROM integrations - WHERE user_id=$1 + FROM + integrations + WHERE + user_id=$1 ` var integration model.Integration err := s.db.QueryRow(query, userID).Scan( @@ -108,16 +106,18 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) { case err == sql.ErrNoRows: return &integration, nil case err != nil: - return &integration, fmt.Errorf("unable to fetch integration row: %v", err) + return &integration, fmt.Errorf(`store: unable to fetch integration row: %v`, err) + default: + return &integration, nil } - - return &integration, nil } // UpdateIntegration saves user integration settings. func (s *Storage) UpdateIntegration(integration *model.Integration) error { query := ` - UPDATE integrations SET + UPDATE + integrations + SET pinboard_enabled=$1, pinboard_token=$2, pinboard_tags=$3, @@ -141,7 +141,8 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { pocket_enabled=$21, pocket_access_token=$22, pocket_consumer_key=$23 - WHERE user_id=$24 + WHERE + user_id=$24 ` _, err := s.db.Exec( query, @@ -172,7 +173,7 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error { ) if err != nil { - return fmt.Errorf("unable to update integration row: %v", err) + return fmt.Errorf(`store: unable to update integration row: %v`, err) } return nil @@ -183,7 +184,7 @@ func (s *Storage) CreateIntegration(userID int64) error { query := `INSERT INTO integrations (user_id) VALUES ($1)` _, err := s.db.Exec(query, userID) if err != nil { - return fmt.Errorf("unable to create integration row: %v", err) + return fmt.Errorf(`store: unable to create integration row: %v`, err) } return nil @@ -192,11 +193,15 @@ func (s *Storage) CreateIntegration(userID int64) error { // HasSaveEntry returns true if the given user can save articles to third-parties. func (s *Storage) HasSaveEntry(userID int64) (result bool) { query := ` - SELECT true FROM integrations - WHERE user_id=$1 AND - (pinboard_enabled='t' OR instapaper_enabled='t' OR wallabag_enabled='t' OR nunux_keeper_enabled='t' OR pocket_enabled='t') + SELECT + true + FROM + integrations + WHERE + user_id=$1 + AND + (pinboard_enabled='t' OR instapaper_enabled='t' OR wallabag_enabled='t' OR nunux_keeper_enabled='t' OR pocket_enabled='t') ` - if err := s.db.QueryRow(query, userID).Scan(&result); err != nil { result = false } diff --git a/storage/job.go b/storage/job.go index c231c97b..3d795dd0 100644 --- a/storage/job.go +++ b/storage/job.go @@ -16,12 +16,14 @@ const maxParsingError = 3 func (s *Storage) NewBatch(batchSize int) (jobs model.JobList, err error) { query := ` SELECT - id, user_id - FROM feeds - WHERE parsing_error_count < $1 AND disabled is false + id, + user_id + FROM + feeds + WHERE + parsing_error_count < $1 AND disabled is false ORDER BY checked_at ASC LIMIT %d ` - return s.fetchBatchRows(fmt.Sprintf(query, batchSize), maxParsingError) } @@ -31,26 +33,28 @@ func (s *Storage) NewUserBatch(userID int64, batchSize int) (jobs model.JobList, // user refresh manually all his feeds to force a refresh. query := ` SELECT - id, user_id - FROM feeds - WHERE user_id=$1 AND disabled is false + id, + user_id + FROM + feeds + WHERE + user_id=$1 AND disabled is false ORDER BY checked_at ASC LIMIT %d ` - return s.fetchBatchRows(fmt.Sprintf(query, batchSize), userID) } func (s *Storage) fetchBatchRows(query string, args ...interface{}) (jobs model.JobList, err error) { rows, err := s.db.Query(query, args...) if err != nil { - return nil, fmt.Errorf("unable to fetch batch of jobs: %v", err) + return nil, fmt.Errorf(`store: unable to fetch batch of jobs: %v`, err) } defer rows.Close() for rows.Next() { var job model.Job if err := rows.Scan(&job.FeedID, &job.UserID); err != nil { - return nil, fmt.Errorf("unable to fetch job: %v", err) + return nil, fmt.Errorf(`store: unable to fetch job: %v`, err) } jobs = append(jobs, job) diff --git a/storage/session.go b/storage/session.go index f57d2eb2..f57b68e8 100644 --- a/storage/session.go +++ b/storage/session.go @@ -44,9 +44,10 @@ func (s *Storage) CreateAppSession() (*model.Session, error) { } func (s *Storage) createAppSession(session *model.Session) (*model.Session, error) { - _, err := s.db.Exec(`INSERT INTO sessions (id, data) VALUES ($1, $2)`, session.ID, session.Data) + query := `INSERT INTO sessions (id, data) VALUES ($1, $2)` + _, err := s.db.Exec(query, session.ID, session.Data) if err != nil { - return nil, fmt.Errorf("unable to create app session: %v", err) + return nil, fmt.Errorf(`store: unable to create app session: %v`, err) } return session, nil @@ -54,13 +55,17 @@ func (s *Storage) createAppSession(session *model.Session) (*model.Session, erro // UpdateAppSessionField updates only one session field. func (s *Storage) UpdateAppSessionField(sessionID, field string, value interface{}) error { - query := `UPDATE sessions - SET data = jsonb_set(data, '{%s}', to_jsonb($1::text), true) - WHERE id=$2` - + query := ` + UPDATE + sessions + SET + data = jsonb_set(data, '{%s}', to_jsonb($1::text), true) + WHERE + id=$2 + ` _, err := s.db.Exec(fmt.Sprintf(query, field), value, sessionID) if err != nil { - return fmt.Errorf("unable to update session field: %v", err) + return fmt.Errorf(`store: unable to update session field: %v`, err) } return nil @@ -76,13 +81,14 @@ func (s *Storage) AppSession(id string) (*model.Session, error) { &session.Data, ) - if err == sql.ErrNoRows { - return nil, fmt.Errorf("session not found: %s", id) - } else if err != nil { - return nil, fmt.Errorf("unable to fetch session: %v", err) + switch { + case err == sql.ErrNoRows: + return nil, fmt.Errorf(`store: session not found: %s`, id) + case err != nil: + return nil, fmt.Errorf(`store: unable to fetch session: %v`, err) + default: + return &session, nil } - - return &session, nil } // FlushAllSessions removes all sessions from the database. @@ -102,10 +108,13 @@ func (s *Storage) FlushAllSessions() (err error) { // CleanOldSessions removes sessions older than specified days. func (s *Storage) CleanOldSessions(days int) int64 { - query := fmt.Sprintf(`DELETE FROM sessions - WHERE id IN (SELECT id FROM sessions WHERE created_at < now() - interval '%d days')`, days) - - result, err := s.db.Exec(query) + query := ` + DELETE FROM + sessions + WHERE + id IN (SELECT id FROM sessions WHERE created_at < now() - interval '%d days') + ` + result, err := s.db.Exec(fmt.Sprintf(query, days)) if err != nil { return 0 } diff --git a/storage/timezone.go b/storage/timezone.go index e5174ca8..d21ac262 100644 --- a/storage/timezone.go +++ b/storage/timezone.go @@ -14,14 +14,14 @@ func (s *Storage) Timezones() (map[string]string, error) { timezones := make(map[string]string) rows, err := s.db.Query(`SELECT name FROM pg_timezone_names() ORDER BY name ASC`) if err != nil { - return nil, fmt.Errorf("unable to fetch timezones: %v", err) + return nil, fmt.Errorf(`store: unable to fetch timezones: %v`, err) } defer rows.Close() for rows.Next() { var timezone string if err := rows.Scan(&timezone); err != nil { - return nil, fmt.Errorf("unable to fetch timezones row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch timezones row: %v`, err) } if !strings.HasPrefix(timezone, "posix") && !strings.HasPrefix(timezone, "SystemV") && timezone != "localtime" { diff --git a/storage/user.go b/storage/user.go index cd85f8d3..39b50ef6 100644 --- a/storage/user.go +++ b/storage/user.go @@ -18,10 +18,10 @@ import ( // SetLastLogin updates the last login date of a user. func (s *Storage) SetLastLogin(userID int64) error { - query := "UPDATE users SET last_login_at=now() WHERE id=$1" + query := `UPDATE users SET last_login_at=now() WHERE id=$1` _, err := s.db.Exec(query, userID) if err != nil { - return fmt.Errorf("unable to update last login date: %v", err) + return fmt.Errorf(`store: unable to update last login date: %v`, err) } return nil @@ -29,16 +29,16 @@ func (s *Storage) SetLastLogin(userID int64) error { // UserExists checks if a user exists by using the given username. func (s *Storage) UserExists(username string) bool { - var result int - s.db.QueryRow(`SELECT count(*) as c FROM users WHERE username=LOWER($1)`, username).Scan(&result) - return result >= 1 + var result bool + s.db.QueryRow(`SELECT true FROM users WHERE username=LOWER($1)`, username).Scan(&result) + return result } // AnotherUserExists checks if another user exists with the given username. func (s *Storage) AnotherUserExists(userID int64, username string) bool { - var result int - s.db.QueryRow(`SELECT count(*) as c FROM users WHERE id != $1 AND username=LOWER($2)`, userID, username).Scan(&result) - return result >= 1 + var result bool + s.db.QueryRow(`SELECT true FROM users WHERE id != $1 AND username=LOWER($2)`, userID, username).Scan(&result) + return result } // CreateUser creates a new user. @@ -79,7 +79,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) { &user.KeyboardShortcuts, ) if err != nil { - return fmt.Errorf("unable to create user: %v", err) + return fmt.Errorf(`store: unable to create user: %v`, err) } s.CreateCategory(&model.Category{Title: "All", UserID: user.ID}) @@ -92,7 +92,7 @@ func (s *Storage) UpdateExtraField(userID int64, field, value string) error { query := fmt.Sprintf(`UPDATE users SET extra = hstore('%s', $1) WHERE id=$2`, field) _, err := s.db.Exec(query, value, userID) if err != nil { - return fmt.Errorf("unable to update user extra field: %v", err) + return fmt.Errorf(`store: unable to update user extra field: %v`, err) } return nil } @@ -102,7 +102,7 @@ func (s *Storage) RemoveExtraField(userID int64, field string) error { query := `UPDATE users SET extra = delete(extra, $1) WHERE id=$2` _, err := s.db.Exec(query, field, userID) if err != nil { - return fmt.Errorf("unable to remove user extra field: %v", err) + return fmt.Errorf(`store: unable to remove user extra field: %v`, err) } return nil } @@ -142,7 +142,7 @@ func (s *Storage) UpdateUser(user *model.User) error { user.ID, ) if err != nil { - return fmt.Errorf("unable to update user: %v", err) + return fmt.Errorf(`store: unable to update user: %v`, err) } } else { query := ` @@ -171,7 +171,7 @@ func (s *Storage) UpdateUser(user *model.User) error { ) if err != nil { - return fmt.Errorf("unable to update user: %v", err) + return fmt.Errorf(`store: unable to update user: %v`, err) } } @@ -192,14 +192,21 @@ func (s *Storage) UserLanguage(userID int64) (language string) { func (s *Storage) UserByID(userID int64) (*model.User, error) { query := ` SELECT - id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, - last_login_at, extra + id, + username, + is_admin, + theme, + language, + timezone, + entry_direction, + keyboard_shortcuts, + last_login_at, + extra FROM users WHERE id = $1 ` - return s.fetchUser(query, userID) } @@ -207,14 +214,21 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) { func (s *Storage) UserByUsername(username string) (*model.User, error) { query := ` SELECT - id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, - last_login_at, extra + id, + username, + is_admin, + theme, + language, + timezone, + entry_direction, + keyboard_shortcuts, + last_login_at, + extra FROM users WHERE username=LOWER($1) ` - return s.fetchUser(query, username) } @@ -222,14 +236,21 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) { func (s *Storage) UserByExtraField(field, value string) (*model.User, error) { query := ` SELECT - id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, - last_login_at, extra + id, + username, + is_admin, + theme, + language, + timezone, + entry_direction, + keyboard_shortcuts, + last_login_at, + extra FROM users WHERE extra->$1=$2 ` - return s.fetchUser(query, field, value) } @@ -253,7 +274,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err if err == sql.ErrNoRows { return nil, nil } else if err != nil { - return nil, fmt.Errorf("unable to fetch user: %v", err) + return nil, fmt.Errorf(`store: unable to fetch user: %v`, err) } for key, value := range extra.Map { @@ -269,16 +290,16 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err func (s *Storage) RemoveUser(userID int64) error { result, err := s.db.Exec("DELETE FROM users WHERE id = $1", userID) if err != nil { - return fmt.Errorf("unable to remove this user: %v", err) + return fmt.Errorf(`store: unable to remove this user: %v`, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("unable to remove this user: %v", err) + return fmt.Errorf(`store: unable to remove this user: %v`, err) } if count == 0 { - return errors.New("nothing has been removed") + return errors.New(`store: nothing has been removed`) } return nil @@ -288,16 +309,23 @@ func (s *Storage) RemoveUser(userID int64) error { func (s *Storage) Users() (model.Users, error) { query := ` SELECT - id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, - last_login_at, extra + id, + username, + is_admin, + theme, + language, + timezone, + entry_direction, + keyboard_shortcuts, + last_login_at, + extra FROM users ORDER BY username ASC ` - rows, err := s.db.Query(query) if err != nil { - return nil, fmt.Errorf("unable to fetch users: %v", err) + return nil, fmt.Errorf(`store: unable to fetch users: %v`, err) } defer rows.Close() @@ -319,7 +347,7 @@ func (s *Storage) Users() (model.Users, error) { ) if err != nil { - return nil, fmt.Errorf("unable to fetch users row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch users row: %v`, err) } for key, value := range extra.Map { @@ -341,13 +369,13 @@ func (s *Storage) CheckPassword(username, password string) error { err := s.db.QueryRow("SELECT password FROM users WHERE username=$1", username).Scan(&hash) if err == sql.ErrNoRows { - return fmt.Errorf("unable to find this user: %s", username) + return fmt.Errorf(`store: unable to find this user: %s`, username) } else if err != nil { - return fmt.Errorf("unable to fetch user: %v", err) + return fmt.Errorf(`store: unable to fetch user: %v`, err) } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil { - return fmt.Errorf(`invalid password for "%s" (%v)`, username, err) + return fmt.Errorf(`store: invalid password for "%s" (%v)`, username, err) } return nil @@ -362,7 +390,7 @@ func (s *Storage) HasPassword(userID int64) (bool, error) { if err == sql.ErrNoRows { return false, nil } else if err != nil { - return false, fmt.Errorf("unable to execute query: %v", err) + return false, fmt.Errorf(`store: unable to execute query: %v`, err) } if result { diff --git a/storage/user_session.go b/storage/user_session.go index 5fa0f0a8..603a875b 100644 --- a/storage/user_session.go +++ b/storage/user_session.go @@ -14,13 +14,22 @@ import ( // UserSessions returns the list of sessions for the given user. func (s *Storage) UserSessions(userID int64) (model.UserSessions, error) { - query := `SELECT - id, user_id, token, created_at, user_agent, ip - FROM user_sessions - WHERE user_id=$1 ORDER BY id DESC` + query := ` + SELECT + id, + user_id, + token, + created_at, + user_agent, + ip + FROM + user_sessions + WHERE + user_id=$1 ORDER BY id DESC + ` rows, err := s.db.Query(query, userID) if err != nil { - return nil, fmt.Errorf("unable to fetch user sessions: %v", err) + return nil, fmt.Errorf(`store: unable to fetch user sessions: %v`, err) } defer rows.Close() @@ -37,7 +46,7 @@ func (s *Storage) UserSessions(userID int64) (model.UserSessions, error) { ) if err != nil { - return nil, fmt.Errorf("unable to fetch user session row: %v", err) + return nil, fmt.Errorf(`store: unable to fetch user session row: %v`, err) } sessions = append(sessions, &session) @@ -48,16 +57,17 @@ func (s *Storage) UserSessions(userID int64) (model.UserSessions, error) { // CreateUserSession creates a new sessions. func (s *Storage) CreateUserSession(username, userAgent, ip string) (sessionID string, userID int64, err error) { - err = s.db.QueryRow("SELECT id FROM users WHERE username = LOWER($1)", username).Scan(&userID) + query := `SELECT id FROM users WHERE username = LOWER($1)` + err = s.db.QueryRow(query, username).Scan(&userID) if err != nil { - return "", 0, fmt.Errorf("unable to fetch user ID: %v", err) + return "", 0, fmt.Errorf(`store: unable to fetch user ID: %v`, err) } token := crypto.GenerateRandomString(64) - query := "INSERT INTO user_sessions (token, user_id, user_agent, ip) VALUES ($1, $2, $3, $4)" + query = `INSERT INTO user_sessions (token, user_id, user_agent, ip) VALUES ($1, $2, $3, $4)` _, err = s.db.Exec(query, token, userID, userAgent, ip) if err != nil { - return "", 0, fmt.Errorf("unable to create user session: %v", err) + return "", 0, fmt.Errorf(`store: unable to create user session: %v`, err) } return token, userID, nil @@ -67,7 +77,19 @@ func (s *Storage) CreateUserSession(username, userAgent, ip string) (sessionID s func (s *Storage) UserSessionByToken(token string) (*model.UserSession, error) { var session model.UserSession - query := "SELECT id, user_id, token, created_at, user_agent, ip FROM user_sessions WHERE token = $1" + query := ` + SELECT + id, + user_id, + token, + created_at, + user_agent, + ip + FROM + user_sessions + WHERE + token = $1 + ` err := s.db.QueryRow(query, token).Scan( &session.ID, &session.UserID, @@ -77,29 +99,31 @@ func (s *Storage) UserSessionByToken(token string) (*model.UserSession, error) { &session.IP, ) - if err == sql.ErrNoRows { + switch { + case err == sql.ErrNoRows: return nil, nil - } else if err != nil { - return nil, fmt.Errorf("unable to fetch user session: %v", err) + case err != nil: + return nil, fmt.Errorf(`store: unable to fetch user session: %v`, err) + default: + return &session, nil } - - return &session, nil } // RemoveUserSessionByToken remove a session by using the token. func (s *Storage) RemoveUserSessionByToken(userID int64, token string) error { - result, err := s.db.Exec(`DELETE FROM user_sessions WHERE user_id=$1 AND token=$2`, userID, token) + query := `DELETE FROM user_sessions WHERE user_id=$1 AND token=$2` + result, err := s.db.Exec(query, userID, token) if err != nil { - return fmt.Errorf("unable to remove this user session: %v", err) + return fmt.Errorf(`store: unable to remove this user session: %v`, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("unable to remove this user session: %v", err) + return fmt.Errorf(`store: unable to remove this user session: %v`, err) } if count != 1 { - return fmt.Errorf("nothing has been removed") + return fmt.Errorf(`store: nothing has been removed`) } return nil @@ -107,18 +131,19 @@ func (s *Storage) RemoveUserSessionByToken(userID int64, token string) error { // RemoveUserSessionByID remove a session by using the ID. func (s *Storage) RemoveUserSessionByID(userID, sessionID int64) error { - result, err := s.db.Exec(`DELETE FROM user_sessions WHERE user_id=$1 AND id=$2`, userID, sessionID) + query := `DELETE FROM user_sessions WHERE user_id=$1 AND id=$2` + result, err := s.db.Exec(query, userID, sessionID) if err != nil { - return fmt.Errorf("unable to remove this user session: %v", err) + return fmt.Errorf(`store: unable to remove this user session: %v`, err) } count, err := result.RowsAffected() if err != nil { - return fmt.Errorf("unable to remove this user session: %v", err) + return fmt.Errorf(`store: unable to remove this user session: %v`, err) } if count != 1 { - return fmt.Errorf("nothing has been removed") + return fmt.Errorf(`store: nothing has been removed`) } return nil @@ -126,10 +151,13 @@ func (s *Storage) RemoveUserSessionByID(userID, sessionID int64) error { // CleanOldUserSessions removes user sessions older than specified days. func (s *Storage) CleanOldUserSessions(days int) int64 { - query := fmt.Sprintf(`DELETE FROM user_sessions - WHERE id IN (SELECT id FROM user_sessions WHERE created_at < now() - interval '%d days')`, days) - - result, err := s.db.Exec(query) + query := ` + DELETE FROM + user_sessions + WHERE + id IN (SELECT id FROM user_sessions WHERE created_at < now() - interval '%d days') + ` + result, err := s.db.Exec(fmt.Sprintf(query, days)) if err != nil { return 0 }