chore: remove prealloc linter
This commit is contained in:
@@ -10,7 +10,6 @@ linters:
|
|||||||
- loggercheck
|
- loggercheck
|
||||||
- misspell
|
- misspell
|
||||||
- perfsprint
|
- perfsprint
|
||||||
- prealloc
|
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|||||||
+11
-11
@@ -20,17 +20,17 @@ type entriesResponse struct {
|
|||||||
|
|
||||||
// EntryImportRequest represents a manually imported entry for a feed.
|
// EntryImportRequest represents a manually imported entry for a feed.
|
||||||
type EntryImportRequest struct {
|
type EntryImportRequest struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
CommentsURL string `json:"comments_url"`
|
CommentsURL string `json:"comments_url"`
|
||||||
PublishedAt int64 `json:"published_at"`
|
PublishedAt int64 `json:"published_at"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Starred bool `json:"starred"`
|
Starred bool `json:"starred"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
ExternalID string `json:"external_id"`
|
ExternalID string `json:"external_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type feedCreationResponse struct {
|
type feedCreationResponse struct {
|
||||||
FeedID int64 `json:"feed_id"`
|
FeedID int64 `json:"feed_id"`
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
|
|||||||
if csvItemIDs != "" {
|
if csvItemIDs != "" {
|
||||||
var itemIDs []int64
|
var itemIDs []int64
|
||||||
|
|
||||||
for _, strItemID := range strings.Split(csvItemIDs, ",") {
|
for strItemID := range strings.SplitSeq(csvItemIDs, ",") {
|
||||||
strItemID = strings.TrimSpace(strItemID)
|
strItemID = strings.TrimSpace(strItemID)
|
||||||
itemID, _ := strconv.ParseInt(strItemID, 10, 64)
|
itemID, _ := strconv.ParseInt(strItemID, 10, 64)
|
||||||
itemIDs = append(itemIDs, itemID)
|
itemIDs = append(itemIDs, itemID)
|
||||||
|
|||||||
@@ -4,257 +4,257 @@
|
|||||||
package readeck
|
package readeck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateBookmark(t *testing.T) {
|
func TestCreateBookmark(t *testing.T) {
|
||||||
entryURL := "https://example.com/article"
|
entryURL := "https://example.com/article"
|
||||||
entryTitle := "Example Title"
|
entryTitle := "Example Title"
|
||||||
entryContent := "<p>Some HTML content</p>"
|
entryContent := "<p>Some HTML content</p>"
|
||||||
labels := "tag1,tag2"
|
labels := "tag1,tag2"
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
onlyURL bool
|
onlyURL bool
|
||||||
baseURL string
|
baseURL string
|
||||||
apiKey string
|
apiKey string
|
||||||
labels string
|
labels string
|
||||||
entryURL string
|
entryURL string
|
||||||
entryTitle string
|
entryTitle string
|
||||||
entryContent string
|
entryContent string
|
||||||
serverResponse func(w http.ResponseWriter, r *http.Request)
|
serverResponse func(w http.ResponseWriter, r *http.Request)
|
||||||
wantErr bool
|
wantErr bool
|
||||||
errContains string
|
errContains string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "successful bookmark creation with only URL",
|
name: "successful bookmark creation with only URL",
|
||||||
onlyURL: true,
|
onlyURL: true,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
entryURL: entryURL,
|
entryURL: entryURL,
|
||||||
entryTitle: entryTitle,
|
entryTitle: entryTitle,
|
||||||
entryContent: entryContent,
|
entryContent: entryContent,
|
||||||
serverResponse: func(w http.ResponseWriter, r *http.Request) {
|
serverResponse: func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
t.Errorf("expected POST, got %s", r.Method)
|
||||||
}
|
}
|
||||||
if r.URL.Path != "/api/bookmarks/" {
|
if r.URL.Path != "/api/bookmarks/" {
|
||||||
t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
|
t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
|
||||||
}
|
}
|
||||||
if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
|
if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
|
||||||
t.Errorf("expected Authorization Bearer header, got %q", got)
|
t.Errorf("expected Authorization Bearer header, got %q", got)
|
||||||
}
|
}
|
||||||
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
|
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
|
||||||
t.Errorf("expected Content-Type application/json, got %s", ct)
|
t.Errorf("expected Content-Type application/json, got %s", ct)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(r.Body)
|
body, _ := io.ReadAll(r.Body)
|
||||||
var payload map[string]any
|
var payload map[string]any
|
||||||
if err := json.Unmarshal(body, &payload); err != nil {
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
t.Fatalf("failed to parse JSON body: %v", err)
|
t.Fatalf("failed to parse JSON body: %v", err)
|
||||||
}
|
}
|
||||||
if u := payload["url"]; u != entryURL {
|
if u := payload["url"]; u != entryURL {
|
||||||
t.Errorf("expected url %s, got %v", entryURL, u)
|
t.Errorf("expected url %s, got %v", entryURL, u)
|
||||||
}
|
}
|
||||||
if title := payload["title"]; title != entryTitle {
|
if title := payload["title"]; title != entryTitle {
|
||||||
t.Errorf("expected title %s, got %v", entryTitle, title)
|
t.Errorf("expected title %s, got %v", entryTitle, title)
|
||||||
}
|
}
|
||||||
// Labels should be split into an array
|
// Labels should be split into an array
|
||||||
if raw := payload["labels"]; raw == nil {
|
if raw := payload["labels"]; raw == nil {
|
||||||
t.Errorf("expected labels to be set")
|
t.Errorf("expected labels to be set")
|
||||||
} else if arr, ok := raw.([]any); ok {
|
} else if arr, ok := raw.([]any); ok {
|
||||||
if len(arr) != 2 || arr[0] != "tag1" || arr[1] != "tag2" {
|
if len(arr) != 2 || arr[0] != "tag1" || arr[1] != "tag2" {
|
||||||
t.Errorf("unexpected labels: %#v", arr)
|
t.Errorf("unexpected labels: %#v", arr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("labels should be an array, got %T", raw)
|
t.Errorf("labels should be an array, got %T", raw)
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "successful bookmark creation with content (multipart)",
|
name: "successful bookmark creation with content (multipart)",
|
||||||
onlyURL: false,
|
onlyURL: false,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
entryURL: entryURL,
|
entryURL: entryURL,
|
||||||
entryTitle: entryTitle,
|
entryTitle: entryTitle,
|
||||||
entryContent: entryContent,
|
entryContent: entryContent,
|
||||||
serverResponse: func(w http.ResponseWriter, r *http.Request) {
|
serverResponse: func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
t.Errorf("expected POST, got %s", r.Method)
|
||||||
}
|
}
|
||||||
if r.URL.Path != "/api/bookmarks/" {
|
if r.URL.Path != "/api/bookmarks/" {
|
||||||
t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
|
t.Errorf("expected path /api/bookmarks/, got %s", r.URL.Path)
|
||||||
}
|
}
|
||||||
if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
|
if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer ") {
|
||||||
t.Errorf("expected Authorization Bearer header, got %q", got)
|
t.Errorf("expected Authorization Bearer header, got %q", got)
|
||||||
}
|
}
|
||||||
ct := r.Header.Get("Content-Type")
|
ct := r.Header.Get("Content-Type")
|
||||||
if !strings.HasPrefix(ct, "multipart/form-data;") {
|
if !strings.HasPrefix(ct, "multipart/form-data;") {
|
||||||
t.Errorf("expected multipart/form-data, got %s", ct)
|
t.Errorf("expected multipart/form-data, got %s", ct)
|
||||||
}
|
}
|
||||||
boundaryIdx := strings.Index(ct, "boundary=")
|
boundaryIdx := strings.Index(ct, "boundary=")
|
||||||
if boundaryIdx == -1 {
|
if boundaryIdx == -1 {
|
||||||
t.Fatalf("missing multipart boundary in Content-Type: %s", ct)
|
t.Fatalf("missing multipart boundary in Content-Type: %s", ct)
|
||||||
}
|
}
|
||||||
boundary := ct[boundaryIdx+len("boundary="):]
|
boundary := ct[boundaryIdx+len("boundary="):]
|
||||||
mr := multipart.NewReader(r.Body, boundary)
|
mr := multipart.NewReader(r.Body, boundary)
|
||||||
|
|
||||||
seenLabels := []string{}
|
seenLabels := []string{}
|
||||||
var seenURL, seenTitle, seenFeature string
|
var seenURL, seenTitle, seenFeature string
|
||||||
var resourceHeader map[string]any
|
var resourceHeader map[string]any
|
||||||
var resourceBody string
|
var resourceBody string
|
||||||
|
|
||||||
for {
|
for {
|
||||||
part, err := mr.NextPart()
|
part, err := mr.NextPart()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("reading multipart: %v", err)
|
t.Fatalf("reading multipart: %v", err)
|
||||||
}
|
}
|
||||||
name := part.FormName()
|
name := part.FormName()
|
||||||
data, _ := io.ReadAll(part)
|
data, _ := io.ReadAll(part)
|
||||||
switch name {
|
switch name {
|
||||||
case "url":
|
case "url":
|
||||||
seenURL = string(data)
|
seenURL = string(data)
|
||||||
case "title":
|
case "title":
|
||||||
seenTitle = string(data)
|
seenTitle = string(data)
|
||||||
case "feature_find_main":
|
case "feature_find_main":
|
||||||
seenFeature = string(data)
|
seenFeature = string(data)
|
||||||
case "labels":
|
case "labels":
|
||||||
seenLabels = append(seenLabels, string(data))
|
seenLabels = append(seenLabels, string(data))
|
||||||
case "resource":
|
case "resource":
|
||||||
// First line is JSON header, then newline, then content
|
// First line is JSON header, then newline, then content
|
||||||
all := string(data)
|
all := string(data)
|
||||||
idx := strings.IndexByte(all, '\n')
|
idx := strings.IndexByte(all, '\n')
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
t.Fatalf("resource content missing header separator")
|
t.Fatalf("resource content missing header separator")
|
||||||
}
|
}
|
||||||
headerJSON := all[:idx]
|
headerJSON := all[:idx]
|
||||||
resourceBody = all[idx+1:]
|
resourceBody = all[idx+1:]
|
||||||
if err := json.Unmarshal([]byte(headerJSON), &resourceHeader); err != nil {
|
if err := json.Unmarshal([]byte(headerJSON), &resourceHeader); err != nil {
|
||||||
t.Fatalf("invalid resource header JSON: %v", err)
|
t.Fatalf("invalid resource header JSON: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if seenURL != entryURL {
|
if seenURL != entryURL {
|
||||||
t.Errorf("expected url %s, got %s", entryURL, seenURL)
|
t.Errorf("expected url %s, got %s", entryURL, seenURL)
|
||||||
}
|
}
|
||||||
if seenTitle != entryTitle {
|
if seenTitle != entryTitle {
|
||||||
t.Errorf("expected title %s, got %s", entryTitle, seenTitle)
|
t.Errorf("expected title %s, got %s", entryTitle, seenTitle)
|
||||||
}
|
}
|
||||||
if seenFeature != "false" {
|
if seenFeature != "false" {
|
||||||
t.Errorf("expected feature_find_main to be 'false', got %s", seenFeature)
|
t.Errorf("expected feature_find_main to be 'false', got %s", seenFeature)
|
||||||
}
|
}
|
||||||
if len(seenLabels) != 2 || seenLabels[0] != "tag1" || seenLabels[1] != "tag2" {
|
if len(seenLabels) != 2 || seenLabels[0] != "tag1" || seenLabels[1] != "tag2" {
|
||||||
t.Errorf("unexpected labels: %#v", seenLabels)
|
t.Errorf("unexpected labels: %#v", seenLabels)
|
||||||
}
|
}
|
||||||
if resourceHeader == nil {
|
if resourceHeader == nil {
|
||||||
t.Fatalf("missing resource header")
|
t.Fatalf("missing resource header")
|
||||||
}
|
}
|
||||||
if hURL, _ := resourceHeader["url"].(string); hURL != entryURL {
|
if hURL, _ := resourceHeader["url"].(string); hURL != entryURL {
|
||||||
t.Errorf("expected resource header url %s, got %v", entryURL, hURL)
|
t.Errorf("expected resource header url %s, got %v", entryURL, hURL)
|
||||||
}
|
}
|
||||||
if headers, ok := resourceHeader["headers"].(map[string]any); ok {
|
if headers, ok := resourceHeader["headers"].(map[string]any); ok {
|
||||||
if ct, _ := headers["content-type"].(string); ct != "text/html; charset=utf-8" {
|
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)
|
t.Errorf("expected resource header content-type text/html; charset=utf-8, got %v", ct)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("missing resource header 'headers' field")
|
t.Errorf("missing resource header 'headers' field")
|
||||||
}
|
}
|
||||||
if resourceBody != entryContent {
|
if resourceBody != entryContent {
|
||||||
t.Errorf("expected resource body %q, got %q", entryContent, resourceBody)
|
t.Errorf("expected resource body %q, got %q", entryContent, resourceBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error when server returns 400",
|
name: "error when server returns 400",
|
||||||
onlyURL: true,
|
onlyURL: true,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
entryURL: entryURL,
|
entryURL: entryURL,
|
||||||
entryTitle: entryTitle,
|
entryTitle: entryTitle,
|
||||||
entryContent: entryContent,
|
entryContent: entryContent,
|
||||||
serverResponse: func(w http.ResponseWriter, r *http.Request) {
|
serverResponse: func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errContains: "unable to create bookmark",
|
errContains: "unable to create bookmark",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error when missing baseURL or apiKey",
|
name: "error when missing baseURL or apiKey",
|
||||||
onlyURL: true,
|
onlyURL: true,
|
||||||
baseURL: "",
|
baseURL: "",
|
||||||
apiKey: "",
|
apiKey: "",
|
||||||
labels: labels,
|
labels: labels,
|
||||||
entryURL: entryURL,
|
entryURL: entryURL,
|
||||||
entryTitle: entryTitle,
|
entryTitle: entryTitle,
|
||||||
entryContent: entryContent,
|
entryContent: entryContent,
|
||||||
serverResponse: nil,
|
serverResponse: nil,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errContains: "missing base URL or API key",
|
errContains: "missing base URL or API key",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var serverURL string
|
var serverURL string
|
||||||
if tt.serverResponse != nil {
|
if tt.serverResponse != nil {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
|
srv := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
serverURL = srv.URL
|
serverURL = srv.URL
|
||||||
}
|
}
|
||||||
baseURL := tt.baseURL
|
baseURL := tt.baseURL
|
||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = serverURL
|
baseURL = serverURL
|
||||||
}
|
}
|
||||||
apiKey := tt.apiKey
|
apiKey := tt.apiKey
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
apiKey = "test-api-key"
|
apiKey = "test-api-key"
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewClient(baseURL, apiKey, tt.labels, tt.onlyURL)
|
client := NewClient(baseURL, apiKey, tt.labels, tt.onlyURL)
|
||||||
err := client.CreateBookmark(tt.entryURL, tt.entryTitle, tt.entryContent)
|
err := client.CreateBookmark(tt.entryURL, tt.entryTitle, tt.entryContent)
|
||||||
|
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error, got none")
|
t.Fatalf("expected error, got none")
|
||||||
}
|
}
|
||||||
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
||||||
t.Fatalf("expected error containing %q, got %q", tt.errContains, err.Error())
|
t.Fatalf("expected error containing %q, got %q", tt.errContains, err.Error())
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
baseURL := "https://readeck.example.com"
|
baseURL := "https://readeck.example.com"
|
||||||
apiKey := "key"
|
apiKey := "key"
|
||||||
labels := "tag1,tag2"
|
labels := "tag1,tag2"
|
||||||
onlyURL := true
|
onlyURL := true
|
||||||
|
|
||||||
c := NewClient(baseURL, apiKey, labels, onlyURL)
|
c := NewClient(baseURL, apiKey, labels, onlyURL)
|
||||||
if c.baseURL != baseURL {
|
if c.baseURL != baseURL {
|
||||||
t.Errorf("expected baseURL %s, got %s", baseURL, c.baseURL)
|
t.Errorf("expected baseURL %s, got %s", baseURL, c.baseURL)
|
||||||
}
|
}
|
||||||
if c.apiKey != apiKey {
|
if c.apiKey != apiKey {
|
||||||
t.Errorf("expected apiKey %s, got %s", apiKey, c.apiKey)
|
t.Errorf("expected apiKey %s, got %s", apiKey, c.apiKey)
|
||||||
}
|
}
|
||||||
if c.labels != labels {
|
if c.labels != labels {
|
||||||
t.Errorf("expected labels %s, got %s", labels, c.labels)
|
t.Errorf("expected labels %s, got %s", labels, c.labels)
|
||||||
}
|
}
|
||||||
if c.onlyURL != onlyURL {
|
if c.onlyURL != onlyURL {
|
||||||
t.Errorf("expected onlyURL %v, got %v", onlyURL, c.onlyURL)
|
t.Errorf("expected onlyURL %v, got %v", onlyURL, c.onlyURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-28
@@ -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) {
|
func (s *Storage) getEntryIDByHash(tx *sql.Tx, feedID int64, entryHash string) (int64, error) {
|
||||||
var entryID int64
|
var entryID int64
|
||||||
|
|
||||||
err := tx.QueryRow(
|
err := tx.QueryRow(
|
||||||
`SELECT id FROM entries WHERE feed_id=$1 AND hash=$2 LIMIT 1`,
|
`SELECT id FROM entries WHERE feed_id=$1 AND hash=$2 LIMIT 1`,
|
||||||
feedID,
|
feedID,
|
||||||
entryHash,
|
entryHash,
|
||||||
).Scan(&entryID)
|
).Scan(&entryID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(`store: unable to fetch entry ID: %v`, err)
|
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.
|
// 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.
|
// 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) {
|
func (s *Storage) InsertEntryForFeed(userID, feedID int64, entry *model.Entry) (bool, error) {
|
||||||
entry.UserID = userID
|
entry.UserID = userID
|
||||||
entry.FeedID = feedID
|
entry.FeedID = feedID
|
||||||
|
|
||||||
tx, err := s.db.Begin()
|
tx, err := s.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("store: unable to start transaction: %v", err)
|
return false, fmt.Errorf("store: unable to start transaction: %v", err)
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
exists, err := s.entryExists(tx, entry)
|
exists, err := s.entryExists(tx, entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
entryID, err := s.getEntryIDByHash(tx, entry.FeedID, entry.Hash)
|
entryID, err := s.getEntryIDByHash(tx, entry.FeedID, entry.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
entry.ID = entryID
|
entry.ID = entryID
|
||||||
} else {
|
} else {
|
||||||
if err := s.createEntry(tx, entry); err != nil {
|
if err := s.createEntry(tx, entry); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return !exists, nil
|
return !exists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) IsNewEntry(feedID int64, entryHash string) bool {
|
func (s *Storage) IsNewEntry(feedID int64, entryHash string) bool {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|||||||
@@ -91,4 +91,3 @@ func (h *handler) showStarredCategoryEntryPage(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
html.OK(w, r, view.Render("entry"))
|
html.OK(w, r, view.Render("entry"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user