[mirror] yet another tui rss reader github.com/olexsmir/smutok

lil bit more of refactoring

olexsmir.xyz ba3c93e8 e6747ea2

verified
+1 -3
internal/config/config.go
··· 51 51 return nil, cerr 52 52 } 53 53 54 - passwd, err := parsePassword( 55 - config.FreshRSS.Password, 56 - filepath.Dir(configPath)) 54 + passwd, err := parsePassword(config.FreshRSS.Password, filepath.Dir(configPath)) 57 55 if err != nil { 58 56 return nil, err 59 57 }
+4 -6
internal/freshrss/client.go
··· 34 34 } 35 35 36 36 func NewClient(host string) *Client { 37 - // todo: validate host url 38 37 return &Client{ 39 38 host: host, 40 39 client: &http.Client{ ··· 62 61 return "", ErrUnauthorized 63 62 } 64 63 65 - func (g *Client) SetAuthToken(token string) { 66 - // todo: validate token 67 - g.authToken = token 68 - } 64 + func (g *Client) SetAuthToken(token string) { g.authToken = token } 69 65 70 66 func (g Client) GetWriteToken(ctx context.Context) (string, error) { 71 67 var resp string ··· 255 251 } 256 252 257 253 func (g Client) SubscriptionEdit(ctx context.Context, token string, opts EditSubscription) (string, error) { 258 - // todo: action is required 254 + if opts.Action == "" { 255 + return "", ErrInvalidRequest 256 + } 259 257 260 258 body := url.Values{} 261 259 body.Set("T", token)
+1 -1
internal/store/sqlite.go
··· 53 53 return err 54 54 } 55 55 56 - slog.Info("running migration") 56 + slog.Debug("running migration") 57 57 if merr := driver.ApplyChanges(ctx, changes, []amigrate.PlanOption{}...); merr != nil { 58 58 return merr 59 59 }
+31 -82
internal/store/sqlite_articles.go
··· 6 6 "strings" 7 7 ) 8 8 9 - func (s *Sqlite) UpsertArticle(ctx context.Context, timestampUsec, feedID, title, content, author, href string, publishedAt int) error { 10 - tx, err := s.db.Begin() 9 + func (s *Sqlite) UpsertArticle( 10 + ctx context.Context, 11 + timestampUsec, feedID, title, content, author, href string, 12 + publishedAt int, 13 + ) error { 14 + tx, err := s.db.BeginTx(ctx, nil) 11 15 if err != nil { 12 16 return err 13 17 } ··· 26 30 return tx.Commit() 27 31 } 28 32 29 - // can be done like this? 30 - // --sql 31 - // update article_statuses 32 - // set is_starred = case 33 - // when article_id in (%s) then 1 34 - // else 0 35 - // end 36 - 37 33 func (s *Sqlite) SyncReadStatus(ctx context.Context, ids []string) error { 38 - if len(ids) == 0 { 39 - _, err := s.db.ExecContext(ctx, "update article_statuses set is_read = 1") 40 - return err 41 - } 42 - 43 - values := strings.Repeat("(?),", len(ids)) 44 - values = values[:len(values)-1] 45 - 46 - args := make([]any, len(ids)) 47 - for i, v := range ids { 48 - args[i] = v 49 - } 50 - 51 - tx, err := s.db.Begin() 52 - if err != nil { 53 - return err 54 - } 55 - defer tx.Rollback() 56 - 57 - // make read those that are not in list 58 - readQuery := fmt.Sprintf(`--sql 34 + placeholders, args := buildPlaceholdersAndArgs(ids) 35 + query := fmt.Sprintf(`--sql 59 36 update article_statuses 60 - set is_read = true 61 - where article_id not in (%s)`, values) 62 - 63 - if _, err = tx.ExecContext(ctx, readQuery, args...); err != nil { 64 - return err 65 - } 66 - 67 - // make unread those that are in list 68 - unreadQuery := fmt.Sprintf(`--sql 69 - update article_statuses 70 - set is_read = false 71 - where article_id in (%s)`, values) 37 + set is_read = case when article_id in (%s) 38 + then false 39 + else true 40 + end`, placeholders) 72 41 73 - if _, err = tx.ExecContext(ctx, unreadQuery, args...); err != nil { 74 - return err 75 - } 76 - 77 - return tx.Commit() 42 + _, err := s.db.ExecContext(ctx, query, args...) 43 + return err 78 44 } 79 45 80 46 func (s *Sqlite) SyncStarredStatus(ctx context.Context, ids []string) error { 81 - if len(ids) == 0 { 82 - _, err := s.db.ExecContext(ctx, "update article_statuses set is_starred = 0") 83 - return err 84 - } 85 - 86 - values := strings.Repeat("(?),", len(ids)) 87 - values = values[:len(values)-1] 88 - 89 - args := make([]any, len(ids)) 90 - for i, v := range ids { 91 - args[i] = v 92 - } 93 - 94 - tx, err := s.db.Begin() 95 - if err != nil { 96 - return err 97 - } 98 - defer tx.Rollback() 99 - 100 - // make read those that are not in list 101 - readQuery := fmt.Sprintf(`--sql 47 + placeholders, args := buildPlaceholdersAndArgs(ids) 48 + query := fmt.Sprintf(`--sql 102 49 update article_statuses 103 - set is_starred = false 104 - where article_id not in (%s)`, values) 50 + set is_starred = case when article_id in (%s) 51 + then true 52 + else false 53 + end`, placeholders) 105 54 106 - if _, err = tx.ExecContext(ctx, readQuery, args...); err != nil { 107 - return err 108 - } 55 + _, err := s.db.ExecContext(ctx, query, args...) 56 + return err 57 + } 109 58 110 - // make unread those that are in list 111 - unreadQuery := fmt.Sprintf(`--sql 112 - update article_statuses 113 - set is_starred = true 114 - where article_id in (%s)`, values) 59 + func buildPlaceholdersAndArgs(in []string, prefixArgs ...any) (placeholders string, args []any) { 60 + placeholders = strings.Repeat("?,", len(in)) 61 + placeholders = placeholders[:len(placeholders)-1] // trim trailing comma 115 62 116 - if _, err = tx.ExecContext(ctx, unreadQuery, args...); err != nil { 117 - return err 63 + args = make([]any, len(prefixArgs)+len(in)) 64 + copy(args, prefixArgs) 65 + for i, v := range in { 66 + args[len(prefixArgs)+i] = v 118 67 } 119 68 120 - return tx.Commit() 69 + return placeholders, args 121 70 }
+2 -15
internal/store/sqlite_feeds.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 - "strings" 7 6 ) 8 7 9 8 func (s *Sqlite) UpsertSubscription(ctx context.Context, id, title, url, htmlURL string) error { ··· 23 22 } 24 23 25 24 func (s *Sqlite) RemoveNonExistentFeeds(ctx context.Context, currentFeedIDs []string) error { 26 - if len(currentFeedIDs) == 0 { 27 - _, err := s.db.ExecContext(ctx, "delete from feeds") 28 - return err 29 - } 30 - 31 - values := strings.Repeat("(?),", len(currentFeedIDs)) 32 - values = values[:len(values)-1] // trim trailing comma 33 - 25 + placeholders, args := buildPlaceholdersAndArgs(currentFeedIDs) 34 26 query := fmt.Sprintf(`--sql 35 27 DELETE FROM feeds 36 28 WHERE id NOT IN (%s) 37 - `, values) 38 - 39 - args := make([]any, len(currentFeedIDs)) 40 - for i, v := range currentFeedIDs { 41 - args[i] = v 42 - } 29 + `, placeholders) 43 30 44 31 _, err := s.db.ExecContext(ctx, query, args...) 45 32 return err
+1 -3
internal/store/sqlite_folders.go
··· 3 3 import "context" 4 4 5 5 func (s *Sqlite) UpsertTag(ctx context.Context, id string) error { 6 - _, err := s.db.ExecContext(ctx, 7 - `insert or replace into folders (id) values (?)`, 8 - id) 6 + _, err := s.db.ExecContext(ctx, `insert or replace into folders (id) values (?)`, id) 9 7 return err 10 8 }
+15 -25
internal/store/sqlite_pendin_actions.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 - "strings" 7 6 ) 8 7 9 8 type Action int ··· 30 29 } 31 30 } 32 31 32 + var changeArticleStatusQuery = map[Action]string{ 33 + Read: `update article_statuses set is_read = 1 where article_id = ?`, 34 + Unread: `update article_statuses set is_read = 0 where article_id = ?`, 35 + Star: `update article_statuses set is_starred = 1 where article_id = ?`, 36 + Unstar: `update article_statuses set is_starred = 0 where article_id = ?`, 37 + } 38 + 33 39 func (s *Sqlite) ChangeArticleStatus(ctx context.Context, articleID string, action Action) error { 34 - tx, err := s.db.Begin() 40 + tx, err := s.db.BeginTx(ctx, nil) 35 41 if err != nil { 36 42 return err 37 43 } 38 44 defer tx.Rollback() 39 45 40 46 // update article status 41 - var query string 42 - switch action { 43 - case Read: 44 - query = `update article_statuses set is_read = 1 where article_id = ?` 45 - case Unread: 46 - query = `update article_statuses set is_read = 0 where article_id = ?` 47 - case Star: 48 - query = `update article_statuses set is_starred = 1 where article_id = ?` 49 - case Unstar: 50 - query = `update article_statuses set is_starred = 0 where article_id = ?` 51 - } 52 - 53 - e, err := tx.ExecContext(ctx, query, articleID) 47 + e, err := tx.ExecContext(ctx, changeArticleStatusQuery[action], articleID) 54 48 if err != nil { 55 49 return err 56 50 } ··· 97 91 return res, nil 98 92 } 99 93 100 - func (s *Sqlite) DeletePendingActions(ctx context.Context, action Action, articleIDs []string) error { 101 - placeholders := strings.Repeat("(?),", len(articleIDs)) 102 - placeholders = placeholders[:len(placeholders)-1] 103 - 104 - args := make([]any, len(articleIDs)+1) 105 - args[0] = action.String() 106 - for i, v := range articleIDs { 107 - args[i+1] = v 108 - } 109 - 94 + func (s *Sqlite) DeletePendingActions( 95 + ctx context.Context, 96 + action Action, 97 + articleIDs []string, 98 + ) error { 99 + placeholders, args := buildPlaceholdersAndArgs(articleIDs, action.String()) 110 100 query := fmt.Sprintf(`--sql 111 101 delete from pending_actions 112 102 where action = ?
+7 -4
internal/store/sqlite_reader.go
··· 8 8 9 9 func (s *Sqlite) GetLastSyncTime(ctx context.Context) (int64, error) { 10 10 var lut int64 11 - err := s.db.QueryRowContext(ctx, "select last_sync from reader where id = 1 and last_sync is not null").Scan(&lut) 11 + err := s.db.QueryRowContext(ctx, "select last_sync from reader where id = 1 and last_sync is not null"). 12 + Scan(&lut) 12 13 if errors.Is(err, sql.ErrNoRows) { 13 14 return 0, ErrNotFound 14 15 } ··· 25 26 26 27 func (s *Sqlite) GetToken(ctx context.Context) (string, error) { 27 28 var tok string 28 - err := s.db.QueryRowContext(ctx, "select token from reader where id = 1 and token is not null").Scan(&tok) 29 + err := s.db.QueryRowContext(ctx, "select token from reader where id = 1 and token is not null"). 30 + Scan(&tok) 29 31 if errors.Is(err, sql.ErrNoRows) { 30 32 return "", ErrNotFound 31 33 } ··· 34 36 35 37 func (s *Sqlite) SetToken(ctx context.Context, token string) error { 36 38 _, err := s.db.ExecContext(ctx, 37 - `insert into reader (id, write_token) values (1, ?) 39 + `insert into reader (id, token) values (1, ?) 38 40 on conflict(id) do update set token = excluded.token`, 39 41 token) 40 42 return err ··· 42 44 43 45 func (s *Sqlite) GetWriteToken(ctx context.Context) (string, error) { 44 46 var tok string 45 - err := s.db.QueryRowContext(ctx, "select write_token from reader where id = 1 and write_token is not null").Scan(&tok) 47 + err := s.db.QueryRowContext(ctx, "select write_token from reader where id = 1 and write_token is not null"). 48 + Scan(&tok) 46 49 if errors.Is(err, sql.ErrNoRows) { 47 50 return "", ErrNotFound 48 51 }