Explorar o código

chore: remove prealloc linter

Frédéric Guillot hai 2 meses
pai
achega
56a11d1d45

+ 0 - 1
.golangci.yml

@@ -10,7 +10,6 @@ linters:
     - loggercheck
     - misspell
     - perfsprint
-    - prealloc
     - sqlclosecheck
     - staticcheck
     - whitespace

+ 11 - 11
internal/api/payload.go

@@ -20,17 +20,17 @@ type entriesResponse struct {
 
 // EntryImportRequest represents a manually imported entry for a feed.
 type EntryImportRequest struct {
-    URL         string   `json:"url"`
-    Title       string   `json:"title"`
-    Content     string   `json:"content"`
-    Author      string   `json:"author"`
-    CommentsURL string   `json:"comments_url"`
-    PublishedAt int64    `json:"published_at"`
-    Status      string   `json:"status"`
-    Starred     bool     `json:"starred"`
-    Tags        []string `json:"tags"`
-    ExternalID  string   `json:"external_id"`
-}   
+	URL         string   `json:"url"`
+	Title       string   `json:"title"`
+	Content     string   `json:"content"`
+	Author      string   `json:"author"`
+	CommentsURL string   `json:"comments_url"`
+	PublishedAt int64    `json:"published_at"`
+	Status      string   `json:"status"`
+	Starred     bool     `json:"starred"`
+	Tags        []string `json:"tags"`
+	ExternalID  string   `json:"external_id"`
+}
 
 type feedCreationResponse struct {
 	FeedID int64 `json:"feed_id"`

+ 1 - 1
internal/fever/handler.go

@@ -279,7 +279,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
 		if csvItemIDs != "" {
 			var itemIDs []int64
 
-			for _, strItemID := range strings.Split(csvItemIDs, ",") {
+			for strItemID := range strings.SplitSeq(csvItemIDs, ",") {
 				strItemID = strings.TrimSpace(strItemID)
 				itemID, _ := strconv.ParseInt(strItemID, 10, 64)
 				itemIDs = append(itemIDs, itemID)

+ 237 - 237
internal/integration/readeck/readeck_test.go

@@ -4,257 +4,257 @@
 package readeck
 
 import (
-    "encoding/json"
-    "io"
-    "mime/multipart"
-    "net/http"
-    "net/http/httptest"
-    "strings"
-    "testing"
+	"encoding/json"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
 )
 
 func TestCreateBookmark(t *testing.T) {
-    entryURL := "https://example.com/article"
-    entryTitle := "Example Title"
-    entryContent := "<p>Some HTML content</p>"
-    labels := "tag1,tag2"
+	entryURL := "https://example.com/article"
+	entryTitle := "Example Title"
+	entryContent := "<p>Some HTML content</p>"
+	labels := "tag1,tag2"
 
-    tests := []struct {
-        name           string
-        onlyURL        bool
-        baseURL        string
-        apiKey         string
-        labels         string
-        entryURL       string
-        entryTitle     string
-        entryContent   string
-        serverResponse func(w http.ResponseWriter, r *http.Request)
-        wantErr        bool
-        errContains    string
-    }{
-        {
-            name:     "successful bookmark creation with only URL",
-            onlyURL:  true,
-            labels:   labels,
-            entryURL: entryURL,
-            entryTitle: entryTitle,
-            entryContent: entryContent,
-            serverResponse: func(w http.ResponseWriter, r *http.Request) {
-                if r.Method != http.MethodPost {
-                    t.Errorf("expected POST, got %s", r.Method)
-                }
-                if r.URL.Path != "/api/bookmarks/" {
-                    t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
-                }
-                if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
-                    t.Errorf("expected Authorization Bearer header, got %q", got)
-                }
-                if ct := r.Header.Get("Content-Type"); ct != "application/json" {
-                    t.Errorf("expected Content-Type application/json, got %s", ct)
-                }
+	tests := []struct {
+		name           string
+		onlyURL        bool
+		baseURL        string
+		apiKey         string
+		labels         string
+		entryURL       string
+		entryTitle     string
+		entryContent   string
+		serverResponse func(w http.ResponseWriter, r *http.Request)
+		wantErr        bool
+		errContains    string
+	}{
+		{
+			name:         "successful bookmark creation with only URL",
+			onlyURL:      true,
+			labels:       labels,
+			entryURL:     entryURL,
+			entryTitle:   entryTitle,
+			entryContent: entryContent,
+			serverResponse: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPost {
+					t.Errorf("expected POST, got %s", r.Method)
+				}
+				if r.URL.Path != "/api/bookmarks/" {
+					t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
+				}
+				if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
+					t.Errorf("expected Authorization Bearer header, got %q", got)
+				}
+				if ct := r.Header.Get("Content-Type"); ct != "application/json" {
+					t.Errorf("expected Content-Type application/json, got %s", ct)
+				}
 
-                body, _ := io.ReadAll(r.Body)
-                var payload map[string]any
-                if err := json.Unmarshal(body, &payload); err != nil {
-                    t.Fatalf("failed to parse JSON body: %v", err)
-                }
-                if u := payload["url"]; u != entryURL {
-                    t.Errorf("expected url %s, got %v", entryURL, u)
-                }
-                if title := payload["title"]; title != entryTitle {
-                    t.Errorf("expected title %s, got %v", entryTitle, title)
-                }
-                // Labels should be split into an array
-                if raw := payload["labels"]; raw == nil {
-                    t.Errorf("expected labels to be set")
-                } else if arr, ok := raw.([]any); ok {
-                    if len(arr) != 2 || arr[0] != "tag1" || arr[1] != "tag2" {
-                        t.Errorf("unexpected labels: %#v", arr)
-                    }
-                } else {
-                    t.Errorf("labels should be an array, got %T", raw)
-                }
-                w.WriteHeader(http.StatusOK)
-            },
-        },
-        {
-            name:     "successful bookmark creation with content (multipart)",
-            onlyURL:  false,
-            labels:   labels,
-            entryURL: entryURL,
-            entryTitle: entryTitle,
-            entryContent: entryContent,
-            serverResponse: func(w http.ResponseWriter, r *http.Request) {
-                if r.Method != http.MethodPost {
-                    t.Errorf("expected POST, got %s", r.Method)
-                }
-                if r.URL.Path != "/api/bookmarks/" {
-                    t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
-                }
-                if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
-                    t.Errorf("expected Authorization Bearer header, got %q", got)
-                }
-                ct := r.Header.Get("Content-Type")
-                if !strings.HasPrefix(ct, "multipart/form-data;") {
-                    t.Errorf("expected multipart/form-data, got %s", ct)
-                }
-                boundaryIdx := strings.Index(ct, "boundary=")
-                if boundaryIdx == -1 {
-                    t.Fatalf("missing multipart boundary in Content-Type: %s", ct)
-                }
-                boundary := ct[boundaryIdx+len("boundary="):]
-                mr := multipart.NewReader(r.Body, boundary)
+				body, _ := io.ReadAll(r.Body)
+				var payload map[string]any
+				if err := json.Unmarshal(body, &payload); err != nil {
+					t.Fatalf("failed to parse JSON body: %v", err)
+				}
+				if u := payload["url"]; u != entryURL {
+					t.Errorf("expected url %s, got %v", entryURL, u)
+				}
+				if title := payload["title"]; title != entryTitle {
+					t.Errorf("expected title %s, got %v", entryTitle, title)
+				}
+				// Labels should be split into an array
+				if raw := payload["labels"]; raw == nil {
+					t.Errorf("expected labels to be set")
+				} else if arr, ok := raw.([]any); ok {
+					if len(arr) != 2 || arr[0] != "tag1" || arr[1] != "tag2" {
+						t.Errorf("unexpected labels: %#v", arr)
+					}
+				} else {
+					t.Errorf("labels should be an array, got %T", raw)
+				}
+				w.WriteHeader(http.StatusOK)
+			},
+		},
+		{
+			name:         "successful bookmark creation with content (multipart)",
+			onlyURL:      false,
+			labels:       labels,
+			entryURL:     entryURL,
+			entryTitle:   entryTitle,
+			entryContent: entryContent,
+			serverResponse: func(w http.ResponseWriter, r *http.Request) {
+				if r.Method != http.MethodPost {
+					t.Errorf("expected POST, got %s", r.Method)
+				}
+				if r.URL.Path != "/api/bookmarks/" {
+					t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
+				}
+				if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
+					t.Errorf("expected Authorization Bearer header, got %q", got)
+				}
+				ct := r.Header.Get("Content-Type")
+				if !strings.HasPrefix(ct, "multipart/form-data;") {
+					t.Errorf("expected multipart/form-data, got %s", ct)
+				}
+				boundaryIdx := strings.Index(ct, "boundary=")
+				if boundaryIdx == -1 {
+					t.Fatalf("missing multipart boundary in Content-Type: %s", ct)
+				}
+				boundary := ct[boundaryIdx+len("boundary="):]
+				mr := multipart.NewReader(r.Body, boundary)
 
-                seenLabels := []string{}
-                var seenURL, seenTitle, seenFeature string
-                var resourceHeader map[string]any
-                var resourceBody string
+				seenLabels := []string{}
+				var seenURL, seenTitle, seenFeature string
+				var resourceHeader map[string]any
+				var resourceBody string
 
-                for {
-                    part, err := mr.NextPart()
-                    if err == io.EOF {
-                        break
-                    }
-                    if err != nil {
-                        t.Fatalf("reading multipart: %v", err)
-                    }
-                    name := part.FormName()
-                    data, _ := io.ReadAll(part)
-                    switch name {
-                    case "url":
-                        seenURL = string(data)
-                    case "title":
-                        seenTitle = string(data)
-                    case "feature_find_main":
-                        seenFeature = string(data)
-                    case "labels":
-                        seenLabels = append(seenLabels, string(data))
-                    case "resource":
-                        // First line is JSON header, then newline, then content
-                        all := string(data)
-                        idx := strings.IndexByte(all, '\n')
-                        if idx == -1 {
-                            t.Fatalf("resource content missing header separator")
-                        }
-                        headerJSON := all[:idx]
-                        resourceBody = all[idx+1:]
-                        if err := json.Unmarshal([]byte(headerJSON), &resourceHeader); err != nil {
-                            t.Fatalf("invalid resource header JSON: %v", err)
-                        }
-                    }
-                }
+				for {
+					part, err := mr.NextPart()
+					if err == io.EOF {
+						break
+					}
+					if err != nil {
+						t.Fatalf("reading multipart: %v", err)
+					}
+					name := part.FormName()
+					data, _ := io.ReadAll(part)
+					switch name {
+					case "url":
+						seenURL = string(data)
+					case "title":
+						seenTitle = string(data)
+					case "feature_find_main":
+						seenFeature = string(data)
+					case "labels":
+						seenLabels = append(seenLabels, string(data))
+					case "resource":
+						// First line is JSON header, then newline, then content
+						all := string(data)
+						idx := strings.IndexByte(all, '\n')
+						if idx == -1 {
+							t.Fatalf("resource content missing header separator")
+						}
+						headerJSON := all[:idx]
+						resourceBody = all[idx+1:]
+						if err := json.Unmarshal([]byte(headerJSON), &resourceHeader); err != nil {
+							t.Fatalf("invalid resource header JSON: %v", err)
+						}
+					}
+				}
 
-                if seenURL != entryURL {
-                    t.Errorf("expected url %s, got %s", entryURL, seenURL)
-                }
-                if seenTitle != entryTitle {
-                    t.Errorf("expected title %s, got %s", entryTitle, seenTitle)
-                }
-                if seenFeature != "false" {
-                    t.Errorf("expected feature_find_main to be 'false', got %s", seenFeature)
-                }
-                if len(seenLabels) != 2 || seenLabels[0] != "tag1" || seenLabels[1] != "tag2" {
-                    t.Errorf("unexpected labels: %#v", seenLabels)
-                }
-                if resourceHeader == nil {
-                    t.Fatalf("missing resource header")
-                }
-                if hURL, _ := resourceHeader["url"].(string); hURL != entryURL {
-                    t.Errorf("expected resource header url %s, got %v", entryURL, hURL)
-                }
-                if headers, ok := resourceHeader["headers"].(map[string]any); ok {
-                    if ct, _ := headers["content-type"].(string); ct != "text/html; charset=utf-8" {
-                        t.Errorf("expected resource header content-type text/html; charset=utf-8, got %v", ct)
-                    }
-                } else {
-                    t.Errorf("missing resource header 'headers' field")
-                }
-                if resourceBody != entryContent {
-                    t.Errorf("expected resource body %q, got %q", entryContent, resourceBody)
-                }
+				if seenURL != entryURL {
+					t.Errorf("expected url %s, got %s", entryURL, seenURL)
+				}
+				if seenTitle != entryTitle {
+					t.Errorf("expected title %s, got %s", entryTitle, seenTitle)
+				}
+				if seenFeature != "false" {
+					t.Errorf("expected feature_find_main to be 'false', got %s", seenFeature)
+				}
+				if len(seenLabels) != 2 || seenLabels[0] != "tag1" || seenLabels[1] != "tag2" {
+					t.Errorf("unexpected labels: %#v", seenLabels)
+				}
+				if resourceHeader == nil {
+					t.Fatalf("missing resource header")
+				}
+				if hURL, _ := resourceHeader["url"].(string); hURL != entryURL {
+					t.Errorf("expected resource header url %s, got %v", entryURL, hURL)
+				}
+				if headers, ok := resourceHeader["headers"].(map[string]any); ok {
+					if ct, _ := headers["content-type"].(string); ct != "text/html; charset=utf-8" {
+						t.Errorf("expected resource header content-type text/html; charset=utf-8, got %v", ct)
+					}
+				} else {
+					t.Errorf("missing resource header 'headers' field")
+				}
+				if resourceBody != entryContent {
+					t.Errorf("expected resource body %q, got %q", entryContent, resourceBody)
+				}
 
-                w.WriteHeader(http.StatusOK)
-            },
-        },
-        {
-            name:        "error when server returns 400",
-            onlyURL:     true,
-            labels:      labels,
-            entryURL:    entryURL,
-            entryTitle:  entryTitle,
-            entryContent: entryContent,
-            serverResponse: func(w http.ResponseWriter, r *http.Request) {
-                w.WriteHeader(http.StatusBadRequest)
-            },
-            wantErr:     true,
-            errContains: "unable to create bookmark",
-        },
-        {
-            name:        "error when missing baseURL or apiKey",
-            onlyURL:     true,
-            baseURL:     "",
-            apiKey:      "",
-            labels:      labels,
-            entryURL:    entryURL,
-            entryTitle:  entryTitle,
-            entryContent: entryContent,
-            serverResponse: nil,
-            wantErr:     true,
-            errContains: "missing base URL or API key",
-        },
-    }
+				w.WriteHeader(http.StatusOK)
+			},
+		},
+		{
+			name:         "error when server returns 400",
+			onlyURL:      true,
+			labels:       labels,
+			entryURL:     entryURL,
+			entryTitle:   entryTitle,
+			entryContent: entryContent,
+			serverResponse: func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusBadRequest)
+			},
+			wantErr:     true,
+			errContains: "unable to create bookmark",
+		},
+		{
+			name:           "error when missing baseURL or apiKey",
+			onlyURL:        true,
+			baseURL:        "",
+			apiKey:         "",
+			labels:         labels,
+			entryURL:       entryURL,
+			entryTitle:     entryTitle,
+			entryContent:   entryContent,
+			serverResponse: nil,
+			wantErr:        true,
+			errContains:    "missing base URL or API key",
+		},
+	}
 
-    for _, tt := range tests {
-        t.Run(tt.name, func(t *testing.T) {
-            var serverURL string
-            if tt.serverResponse != nil {
-                srv := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
-                defer srv.Close()
-                serverURL = srv.URL
-            }
-            baseURL := tt.baseURL
-            if baseURL == "" {
-                baseURL = serverURL
-            }
-            apiKey := tt.apiKey
-            if apiKey == "" {
-                apiKey = "test-api-key"
-            }
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var serverURL string
+			if tt.serverResponse != nil {
+				srv := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
+				defer srv.Close()
+				serverURL = srv.URL
+			}
+			baseURL := tt.baseURL
+			if baseURL == "" {
+				baseURL = serverURL
+			}
+			apiKey := tt.apiKey
+			if apiKey == "" {
+				apiKey = "test-api-key"
+			}
 
-            client := NewClient(baseURL, apiKey, tt.labels, tt.onlyURL)
-            err := client.CreateBookmark(tt.entryURL, tt.entryTitle, tt.entryContent)
+			client := NewClient(baseURL, apiKey, tt.labels, tt.onlyURL)
+			err := client.CreateBookmark(tt.entryURL, tt.entryTitle, tt.entryContent)
 
-            if tt.wantErr {
-                if err == nil {
-                    t.Fatalf("expected error, got none")
-                }
-                if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
-                    t.Fatalf("expected error containing %q, got %q", tt.errContains, err.Error())
-                }
-            } else if err != nil {
-                t.Fatalf("unexpected error: %v", err)
-            }
-        })
-    }
+			if tt.wantErr {
+				if err == nil {
+					t.Fatalf("expected error, got none")
+				}
+				if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
+					t.Fatalf("expected error containing %q, got %q", tt.errContains, err.Error())
+				}
+			} else if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+		})
+	}
 }
 
 func TestNewClient(t *testing.T) {
-    baseURL := "https://readeck.example.com"
-    apiKey := "key"
-    labels := "tag1,tag2"
-    onlyURL := true
+	baseURL := "https://readeck.example.com"
+	apiKey := "key"
+	labels := "tag1,tag2"
+	onlyURL := true
 
-    c := NewClient(baseURL, apiKey, labels, onlyURL)
-    if c.baseURL != baseURL {
-        t.Errorf("expected baseURL %s, got %s", baseURL, c.baseURL)
-    }
-    if c.apiKey != apiKey {
-        t.Errorf("expected apiKey %s, got %s", apiKey, c.apiKey)
-    }
-    if c.labels != labels {
-        t.Errorf("expected labels %s, got %s", labels, c.labels)
-    }
-    if c.onlyURL != onlyURL {
-        t.Errorf("expected onlyURL %v, got %v", onlyURL, c.onlyURL)
-    }
+	c := NewClient(baseURL, apiKey, labels, onlyURL)
+	if c.baseURL != baseURL {
+		t.Errorf("expected baseURL %s, got %s", baseURL, c.baseURL)
+	}
+	if c.apiKey != apiKey {
+		t.Errorf("expected apiKey %s, got %s", apiKey, c.apiKey)
+	}
+	if c.labels != labels {
+		t.Errorf("expected labels %s, got %s", labels, c.labels)
+	}
+	if c.onlyURL != onlyURL {
+		t.Errorf("expected onlyURL %v, got %v", onlyURL, c.onlyURL)
+	}
 }

+ 28 - 28
internal/storage/entry.go

@@ -237,55 +237,55 @@ func (s *Storage) entryExists(tx *sql.Tx, entry *model.Entry) (bool, error) {
 }
 
 func (s *Storage) getEntryIDByHash(tx *sql.Tx, feedID int64, entryHash string) (int64, error) {
-    var entryID int64
+	var entryID int64
 
-    err := tx.QueryRow(
-        `SELECT id FROM entries WHERE feed_id=$1 AND hash=$2 LIMIT 1`,
-        feedID,
-        entryHash,
-    ).Scan(&entryID)
+	err := tx.QueryRow(
+		`SELECT id FROM entries WHERE feed_id=$1 AND hash=$2 LIMIT 1`,
+		feedID,
+		entryHash,
+	).Scan(&entryID)
 
-    if err != nil {
-        return 0, fmt.Errorf(`store: unable to fetch entry ID: %v`, err)
-    }
+	if err != nil {
+		return 0, fmt.Errorf(`store: unable to fetch entry ID: %v`, err)
+	}
 
-    return entryID, nil
+	return entryID, nil
 }
 
 // InsertEntryForFeed inserts a single entry into a feed, optionally updating if it already exists.
 // Returns true if a new entry was created, false if an existing one was reused.
 func (s *Storage) InsertEntryForFeed(userID, feedID int64, entry *model.Entry) (bool, error) {
 	entry.UserID = userID
-    entry.FeedID = feedID
+	entry.FeedID = feedID
 
-    tx, err := s.db.Begin()
-    if err != nil {
-        return false, fmt.Errorf("store: unable to start transaction: %v", err)
-    }
+	tx, err := s.db.Begin()
+	if err != nil {
+		return false, fmt.Errorf("store: unable to start transaction: %v", err)
+	}
 	defer tx.Rollback()
 
 	exists, err := s.entryExists(tx, entry)
 	if err != nil {
-    	return false, err
+		return false, err
 	}
 
 	if exists {
-    	entryID, err := s.getEntryIDByHash(tx, entry.FeedID, entry.Hash)
-    	if err != nil {
-        	return false, err
-    	}
-    	entry.ID = entryID
+		entryID, err := s.getEntryIDByHash(tx, entry.FeedID, entry.Hash)
+		if err != nil {
+			return false, err
+		}
+		entry.ID = entryID
 	} else {
-    	if err := s.createEntry(tx, entry); err != nil {
-        	return false, err
-    	}
+		if err := s.createEntry(tx, entry); err != nil {
+			return false, err
+		}
 	}
 
-    if err := tx.Commit(); err != nil {
-        return false, err
-    }
+	if err := tx.Commit(); err != nil {
+		return false, err
+	}
 
-    return !exists, nil
+	return !exists, nil
 }
 
 func (s *Storage) IsNewEntry(feedID int64, entryHash string) bool {

+ 1 - 1
internal/template/functions.go

@@ -10,8 +10,8 @@ import (
 	"math"
 	"net/mail"
 	"net/url"
-	"strconv"
 	"slices"
+	"strconv"
 	"strings"
 	"time"
 

+ 0 - 1
internal/ui/starred_entry_category.go

@@ -91,4 +91,3 @@ func (h *handler) showStarredCategoryEntryPage(w http.ResponseWriter, r *http.Re
 
 	html.OK(w, r, view.Render("entry"))
 }
-