|
|
|
@ -9,6 +9,7 @@ import ( |
|
|
|
|
"fmt" |
|
|
|
|
"strings" |
|
|
|
|
|
|
|
|
|
"miniflux.app/logger" |
|
|
|
|
"miniflux.app/model" |
|
|
|
|
|
|
|
|
|
"github.com/lib/pq/hstore" |
|
|
|
@ -357,6 +358,15 @@ func (s *Storage) RemoveUser(userID int64) error { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// RemoveUserAsync deletes user data without locking the database.
|
|
|
|
|
func (s *Storage) RemoveUserAsync(userID int64) { |
|
|
|
|
go func() { |
|
|
|
|
deleteUserFeeds(s.db, userID) |
|
|
|
|
s.db.Exec(`DELETE FROM users WHERE id=$1`, userID) |
|
|
|
|
s.db.Exec(`DELETE FROM integrations WHERE user_id=$1`, userID) |
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Users returns all users.
|
|
|
|
|
func (s *Storage) Users() (model.Users, error) { |
|
|
|
|
query := ` |
|
|
|
@ -459,3 +469,81 @@ func hashPassword(password string) (string, error) { |
|
|
|
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) |
|
|
|
|
return string(bytes), err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteUserFeeds(db *sql.DB, userID int64) { |
|
|
|
|
query := `SELECT id FROM feeds WHERE user_id=$1` |
|
|
|
|
rows, err := db.Query(query, userID) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.Error(`store: unable to get user feeds: %v`, err) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
defer rows.Close() |
|
|
|
|
|
|
|
|
|
var feedIDs []int64 |
|
|
|
|
for rows.Next() { |
|
|
|
|
var feedID int64 |
|
|
|
|
rows.Scan(&feedID) |
|
|
|
|
feedIDs = append(feedIDs, feedID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
worker := func(jobs <-chan int64, results chan<- bool) { |
|
|
|
|
for feedID := range jobs { |
|
|
|
|
deleteUserEntries(db, userID, feedID) |
|
|
|
|
db.Exec(`DELETE FROM feeds WHERE id=$1`, feedID) |
|
|
|
|
results <- true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const numWorkers = 3 |
|
|
|
|
numJobs := len(feedIDs) |
|
|
|
|
jobs := make(chan int64, numJobs) |
|
|
|
|
results := make(chan bool, numJobs) |
|
|
|
|
|
|
|
|
|
for w := 0; w < numWorkers; w++ { |
|
|
|
|
go worker(jobs, results) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for j := 0; j < numJobs; j++ { |
|
|
|
|
jobs <- feedIDs[j] |
|
|
|
|
} |
|
|
|
|
close(jobs) |
|
|
|
|
|
|
|
|
|
for a := 1; a <= numJobs; a++ { |
|
|
|
|
<-results |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteUserEntries(db *sql.DB, userID int64, feedID int64) { |
|
|
|
|
query := `SELECT id FROM entries WHERE user_id=$1 AND feed_id=$2` |
|
|
|
|
rows, err := db.Query(query, userID, feedID) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.Error(`store: unable to get user feed entries: %v`, err) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
defer rows.Close() |
|
|
|
|
|
|
|
|
|
for rows.Next() { |
|
|
|
|
var entryID int64 |
|
|
|
|
rows.Scan(&entryID) |
|
|
|
|
deleteUserEnclosures(db, userID, entryID) |
|
|
|
|
db.Exec(`DELETE FROM entries WHERE id=$1`, entryID) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func deleteUserEnclosures(db *sql.DB, userID int64, entryID int64) { |
|
|
|
|
query := `SELECT id FROM enclosures WHERE user_id=$1 AND entry_id=$2` |
|
|
|
|
rows, err := db.Query(query, userID, entryID) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.Error(`store: unable to get user entry enclosures: %v`, err) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
defer rows.Close() |
|
|
|
|
|
|
|
|
|
for rows.Next() { |
|
|
|
|
var enclosureID int64 |
|
|
|
|
rows.Scan(&enclosureID) |
|
|
|
|
go func() { |
|
|
|
|
db.Exec(`DELETE FROM enclosures WHERE id=$1`, enclosureID) |
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|