Improve storage module
This commit is contained in:
parent
e38333e272
commit
d3883126bf
13 changed files with 505 additions and 317 deletions
|
@ -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 {
|
||||
|
|
|
@ -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,29 +34,33 @@ 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.
|
||||
func (s *Storage) CategoryByTitle(userID int64, title string) (*model.Category, error) {
|
||||
|
@ -64,21 +68,23 @@ 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)
|
||||
}
|
||||
|
||||
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.
|
||||
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,
|
||||
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`
|
||||
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)
|
||||
|
@ -129,7 +141,8 @@ func (s *Storage) CreateCategory(category *model.Category) error {
|
|||
(user_id, title)
|
||||
VALUES
|
||||
($1, $2)
|
||||
RETURNING id
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
@ -52,7 +62,8 @@ func (s *Storage) CreateEnclosure(enclosure *model.Enclosure) error {
|
|||
(url, size, mime_type, entry_id, user_id)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
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
|
||||
|
|
129
storage/entry.go
129
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()
|
||||
|
@ -68,7 +79,8 @@ func (s *Storage) createEntry(entry *model.Entry) error {
|
|||
(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
|
||||
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,
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
159
storage/feed.go
159
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,
|
||||
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,9 +140,16 @@ 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,
|
||||
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,
|
||||
|
@ -139,12 +162,13 @@ func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) {
|
|||
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.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,
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
@ -74,7 +77,8 @@ func (s *Storage) CreateIcon(icon *model.Icon) error {
|
|||
(hash, mime_type, content)
|
||||
VALUES
|
||||
($1, $2, $3)
|
||||
RETURNING id
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,14 +81,15 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// FlushAllSessions removes all sessions from the database.
|
||||
func (s *Storage) FlushAllSessions() (err error) {
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue