8 files changed,
62 insertions(+),
139 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2025-12-26 20:09:54 +0200
Change ID:
ylokpoptyxluzzwqppvsknmkymxyuqqy
Parent:
e6747ea
M
internal/config/config.go
@@ -51,9 +51,7 @@ if cerr := toml.Unmarshal(configRaw, &config); cerr != nil {
return nil, cerr } - passwd, err := parsePassword( - config.FreshRSS.Password, - filepath.Dir(configPath)) + passwd, err := parsePassword(config.FreshRSS.Password, filepath.Dir(configPath)) if err != nil { return nil, err }
M
internal/freshrss/client.go
@@ -34,7 +34,6 @@ client *http.Client
} func NewClient(host string) *Client { - // todo: validate host url return &Client{ host: host, client: &http.Client{@@ -62,10 +61,7 @@
return "", ErrUnauthorized } -func (g *Client) SetAuthToken(token string) { - // todo: validate token - g.authToken = token -} +func (g *Client) SetAuthToken(token string) { g.authToken = token } func (g Client) GetWriteToken(ctx context.Context) (string, error) { var resp string@@ -255,7 +251,9 @@ Remove string
} func (g Client) SubscriptionEdit(ctx context.Context, token string, opts EditSubscription) (string, error) { - // todo: action is required + if opts.Action == "" { + return "", ErrInvalidRequest + } body := url.Values{} body.Set("T", token)
M
internal/store/sqlite.go
@@ -53,7 +53,7 @@ if err != nil {
return err } - slog.Info("running migration") + slog.Debug("running migration") if merr := driver.ApplyChanges(ctx, changes, []amigrate.PlanOption{}...); merr != nil { return merr }
M
internal/store/sqlite_articles.go
@@ -6,8 +6,12 @@ "fmt"
"strings" ) -func (s *Sqlite) UpsertArticle(ctx context.Context, timestampUsec, feedID, title, content, author, href string, publishedAt int) error { - tx, err := s.db.Begin() +func (s *Sqlite) UpsertArticle( + ctx context.Context, + timestampUsec, feedID, title, content, author, href string, + publishedAt int, +) error { + tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err }@@ -26,96 +30,41 @@
return tx.Commit() } -// can be done like this? -// --sql -// update article_statuses -// set is_starred = case -// when article_id in (%s) then 1 -// else 0 -// end - func (s *Sqlite) SyncReadStatus(ctx context.Context, ids []string) error { - if len(ids) == 0 { - _, err := s.db.ExecContext(ctx, "update article_statuses set is_read = 1") - return err - } - - values := strings.Repeat("(?),", len(ids)) - values = values[:len(values)-1] - - args := make([]any, len(ids)) - for i, v := range ids { - args[i] = v - } - - tx, err := s.db.Begin() - if err != nil { - return err - } - defer tx.Rollback() - - // make read those that are not in list - readQuery := fmt.Sprintf(`--sql + placeholders, args := buildPlaceholdersAndArgs(ids) + query := fmt.Sprintf(`--sql update article_statuses - set is_read = true - where article_id not in (%s)`, values) - - if _, err = tx.ExecContext(ctx, readQuery, args...); err != nil { - return err - } - - // make unread those that are in list - unreadQuery := fmt.Sprintf(`--sql - update article_statuses - set is_read = false - where article_id in (%s)`, values) + set is_read = case when article_id in (%s) + then false + else true + end`, placeholders) - if _, err = tx.ExecContext(ctx, unreadQuery, args...); err != nil { - return err - } - - return tx.Commit() + _, err := s.db.ExecContext(ctx, query, args...) + return err } func (s *Sqlite) SyncStarredStatus(ctx context.Context, ids []string) error { - if len(ids) == 0 { - _, err := s.db.ExecContext(ctx, "update article_statuses set is_starred = 0") - return err - } - - values := strings.Repeat("(?),", len(ids)) - values = values[:len(values)-1] - - args := make([]any, len(ids)) - for i, v := range ids { - args[i] = v - } - - tx, err := s.db.Begin() - if err != nil { - return err - } - defer tx.Rollback() - - // make read those that are not in list - readQuery := fmt.Sprintf(`--sql + placeholders, args := buildPlaceholdersAndArgs(ids) + query := fmt.Sprintf(`--sql update article_statuses - set is_starred = false - where article_id not in (%s)`, values) + set is_starred = case when article_id in (%s) + then true + else false + end`, placeholders) - if _, err = tx.ExecContext(ctx, readQuery, args...); err != nil { - return err - } + _, err := s.db.ExecContext(ctx, query, args...) + return err +} - // make unread those that are in list - unreadQuery := fmt.Sprintf(`--sql - update article_statuses - set is_starred = true - where article_id in (%s)`, values) +func buildPlaceholdersAndArgs(in []string, prefixArgs ...any) (placeholders string, args []any) { + placeholders = strings.Repeat("?,", len(in)) + placeholders = placeholders[:len(placeholders)-1] // trim trailing comma - if _, err = tx.ExecContext(ctx, unreadQuery, args...); err != nil { - return err + args = make([]any, len(prefixArgs)+len(in)) + copy(args, prefixArgs) + for i, v := range in { + args[len(prefixArgs)+i] = v } - return tx.Commit() + return placeholders, args }
M
internal/store/sqlite_feeds.go
@@ -3,7 +3,6 @@
import ( "context" "fmt" - "strings" ) func (s *Sqlite) UpsertSubscription(ctx context.Context, id, title, url, htmlURL string) error {@@ -23,23 +22,11 @@ return err
} func (s *Sqlite) RemoveNonExistentFeeds(ctx context.Context, currentFeedIDs []string) error { - if len(currentFeedIDs) == 0 { - _, err := s.db.ExecContext(ctx, "delete from feeds") - return err - } - - values := strings.Repeat("(?),", len(currentFeedIDs)) - values = values[:len(values)-1] // trim trailing comma - + placeholders, args := buildPlaceholdersAndArgs(currentFeedIDs) query := fmt.Sprintf(`--sql DELETE FROM feeds WHERE id NOT IN (%s) - `, values) - - args := make([]any, len(currentFeedIDs)) - for i, v := range currentFeedIDs { - args[i] = v - } + `, placeholders) _, err := s.db.ExecContext(ctx, query, args...) return err
M
internal/store/sqlite_folders.go
@@ -3,8 +3,6 @@
import "context" func (s *Sqlite) UpsertTag(ctx context.Context, id string) error { - _, err := s.db.ExecContext(ctx, - `insert or replace into folders (id) values (?)`, - id) + _, err := s.db.ExecContext(ctx, `insert or replace into folders (id) values (?)`, id) return err }
M
internal/store/sqlite_pendin_actions.go
@@ -3,7 +3,6 @@
import ( "context" "fmt" - "strings" ) type Action int@@ -30,27 +29,22 @@ return "unsupported"
} } +var changeArticleStatusQuery = map[Action]string{ + Read: `update article_statuses set is_read = 1 where article_id = ?`, + Unread: `update article_statuses set is_read = 0 where article_id = ?`, + Star: `update article_statuses set is_starred = 1 where article_id = ?`, + Unstar: `update article_statuses set is_starred = 0 where article_id = ?`, +} + func (s *Sqlite) ChangeArticleStatus(ctx context.Context, articleID string, action Action) error { - tx, err := s.db.Begin() + tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() // update article status - var query string - switch action { - case Read: - query = `update article_statuses set is_read = 1 where article_id = ?` - case Unread: - query = `update article_statuses set is_read = 0 where article_id = ?` - case Star: - query = `update article_statuses set is_starred = 1 where article_id = ?` - case Unstar: - query = `update article_statuses set is_starred = 0 where article_id = ?` - } - - e, err := tx.ExecContext(ctx, query, articleID) + e, err := tx.ExecContext(ctx, changeArticleStatusQuery[action], articleID) if err != nil { return err }@@ -97,16 +91,12 @@
return res, nil } -func (s *Sqlite) DeletePendingActions(ctx context.Context, action Action, articleIDs []string) error { - placeholders := strings.Repeat("(?),", len(articleIDs)) - placeholders = placeholders[:len(placeholders)-1] - - args := make([]any, len(articleIDs)+1) - args[0] = action.String() - for i, v := range articleIDs { - args[i+1] = v - } - +func (s *Sqlite) DeletePendingActions( + ctx context.Context, + action Action, + articleIDs []string, +) error { + placeholders, args := buildPlaceholdersAndArgs(articleIDs, action.String()) query := fmt.Sprintf(`--sql delete from pending_actions where action = ?
M
internal/store/sqlite_reader.go
@@ -8,7 +8,8 @@ )
func (s *Sqlite) GetLastSyncTime(ctx context.Context) (int64, error) { var lut int64 - err := s.db.QueryRowContext(ctx, "select last_sync from reader where id = 1 and last_sync is not null").Scan(&lut) + err := s.db.QueryRowContext(ctx, "select last_sync from reader where id = 1 and last_sync is not null"). + Scan(&lut) if errors.Is(err, sql.ErrNoRows) { return 0, ErrNotFound }@@ -25,7 +26,8 @@ }
func (s *Sqlite) GetToken(ctx context.Context) (string, error) { var tok string - err := s.db.QueryRowContext(ctx, "select token from reader where id = 1 and token is not null").Scan(&tok) + err := s.db.QueryRowContext(ctx, "select token from reader where id = 1 and token is not null"). + Scan(&tok) if errors.Is(err, sql.ErrNoRows) { return "", ErrNotFound }@@ -34,7 +36,7 @@ }
func (s *Sqlite) SetToken(ctx context.Context, token string) error { _, err := s.db.ExecContext(ctx, - `insert into reader (id, write_token) values (1, ?) + `insert into reader (id, token) values (1, ?) on conflict(id) do update set token = excluded.token`, token) return err@@ -42,7 +44,8 @@ }
func (s *Sqlite) GetWriteToken(ctx context.Context) (string, error) { var tok string - err := s.db.QueryRowContext(ctx, "select write_token from reader where id = 1 and write_token is not null").Scan(&tok) + err := s.db.QueryRowContext(ctx, "select write_token from reader where id = 1 and write_token is not null"). + Scan(&tok) if errors.Is(err, sql.ErrNoRows) { return "", ErrNotFound }