aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/api/api.go1
-rw-r--r--internal/api/entry.go55
-rw-r--r--internal/model/entry.go16
-rw-r--r--internal/storage/entry.go37
-rw-r--r--internal/tests/entry_test.go34
-rw-r--r--internal/ui/entry_scraper.go2
-rw-r--r--internal/validator/entry.go19
7 files changed, 132 insertions, 32 deletions
diff --git a/internal/api/api.go b/internal/api/api.go
index e00e8272..d6a36376 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -63,6 +63,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
sr.HandleFunc("/entries", handler.getEntries).Methods(http.MethodGet)
sr.HandleFunc("/entries", handler.setEntryStatus).Methods(http.MethodPut)
sr.HandleFunc("/entries/{entryID}", handler.getEntry).Methods(http.MethodGet)
+ sr.HandleFunc("/entries/{entryID}", handler.updateEntry).Methods(http.MethodPut)
sr.HandleFunc("/entries/{entryID}/bookmark", handler.toggleBookmark).Methods(http.MethodPut)
sr.HandleFunc("/entries/{entryID}/save", handler.saveEntry).Methods(http.MethodPost)
sr.HandleFunc("/entries/{entryID}/fetch-content", handler.fetchContent).Methods(http.MethodGet)
diff --git a/internal/api/entry.go b/internal/api/entry.go
index e6c00e3a..2af2b3c7 100644
--- a/internal/api/entry.go
+++ b/internal/api/entry.go
@@ -18,6 +18,7 @@ import (
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/proxy"
"miniflux.app/v2/internal/reader/processor"
+ "miniflux.app/v2/internal/reader/readingtime"
"miniflux.app/v2/internal/storage"
"miniflux.app/v2/internal/urllib"
"miniflux.app/v2/internal/validator"
@@ -232,6 +233,58 @@ func (h *handler) saveEntry(w http.ResponseWriter, r *http.Request) {
json.Accepted(w, r)
}
+func (h *handler) updateEntry(w http.ResponseWriter, r *http.Request) {
+ var entryUpdateRequest model.EntryUpdateRequest
+ if err := json_parser.NewDecoder(r.Body).Decode(&entryUpdateRequest); err != nil {
+ json.BadRequest(w, r, err)
+ return
+ }
+
+ if err := validator.ValidateEntryModification(&entryUpdateRequest); err != nil {
+ json.BadRequest(w, r, err)
+ return
+ }
+
+ loggedUserID := request.UserID(r)
+ entryID := request.RouteInt64Param(r, "entryID")
+
+ entryBuilder := h.store.NewEntryQueryBuilder(loggedUserID)
+ entryBuilder.WithEntryID(entryID)
+ entryBuilder.WithoutStatus(model.EntryStatusRemoved)
+
+ entry, err := entryBuilder.GetEntry()
+ if err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+
+ if entry == nil {
+ json.NotFound(w, r)
+ return
+ }
+
+ user, err := h.store.UserByID(loggedUserID)
+ if err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+
+ if user == nil {
+ json.NotFound(w, r)
+ return
+ }
+
+ entryUpdateRequest.Patch(entry)
+ entry.ReadingTime = readingtime.EstimateReadingTime(entry.Content, user.DefaultReadingSpeed, user.CJKReadingSpeed)
+
+ if err := h.store.UpdateEntryTitleAndContent(entry); err != nil {
+ json.ServerError(w, r, err)
+ return
+ }
+
+ json.Created(w, r, entry)
+}
+
func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
loggedUserID := request.UserID(r)
entryID := request.RouteInt64Param(r, "entryID")
@@ -251,7 +304,7 @@ func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
return
}
- user, err := h.store.UserByID(entry.UserID)
+ user, err := h.store.UserByID(loggedUserID)
if err != nil {
json.ServerError(w, r, err)
return
diff --git a/internal/model/entry.go b/internal/model/entry.go
index 87dcae35..7631119a 100644
--- a/internal/model/entry.go
+++ b/internal/model/entry.go
@@ -58,3 +58,19 @@ type EntriesStatusUpdateRequest struct {
EntryIDs []int64 `json:"entry_ids"`
Status string `json:"status"`
}
+
+// EntryUpdateRequest represents a request to update an entry.
+type EntryUpdateRequest struct {
+ Title *string `json:"title"`
+ Content *string `json:"content"`
+}
+
+func (e *EntryUpdateRequest) Patch(entry *Entry) {
+ if e.Title != nil && *e.Title != "" {
+ entry.Title = *e.Title
+ }
+
+ if e.Content != nil && *e.Content != "" {
+ entry.Content = *e.Content
+ }
+}
diff --git a/internal/storage/entry.go b/internal/storage/entry.go
index 0ddbeebf..647db94f 100644
--- a/internal/storage/entry.go
+++ b/internal/storage/entry.go
@@ -67,42 +67,25 @@ func (s *Storage) NewEntryQueryBuilder(userID int64) *EntryQueryBuilder {
return NewEntryQueryBuilder(s, userID)
}
-// UpdateEntryContent updates entry content.
-func (s *Storage) UpdateEntryContent(entry *model.Entry) error {
- tx, err := s.db.Begin()
- if err != nil {
- return err
- }
-
+// UpdateEntryTitleAndContent updates entry title and content.
+func (s *Storage) UpdateEntryTitleAndContent(entry *model.Entry) error {
query := `
UPDATE
entries
SET
- content=$1, reading_time=$2
+ title=$1,
+ content=$2,
+ reading_time=$3,
+ document_vectors = setweight(to_tsvector(left(coalesce($1, ''), 500000)), 'A') || setweight(to_tsvector(left(coalesce($2, ''), 500000)), 'B')
WHERE
- id=$3 AND user_id=$4
+ id=$4 AND user_id=$5
`
- _, err = tx.Exec(query, entry.Content, entry.ReadingTime, entry.ID, entry.UserID)
- if err != nil {
- tx.Rollback()
- return fmt.Errorf(`store: unable to update content of entry #%d: %v`, entry.ID, err)
- }
- query = `
- UPDATE
- entries
- SET
- document_vectors = setweight(to_tsvector(left(coalesce(title, ''), 500000)), 'A') || setweight(to_tsvector(left(coalesce(content, ''), 500000)), 'B')
- WHERE
- id=$1 AND user_id=$2
- `
- _, err = tx.Exec(query, entry.ID, entry.UserID)
- if err != nil {
- tx.Rollback()
- return fmt.Errorf(`store: unable to update content of entry #%d: %v`, entry.ID, err)
+ if _, err := s.db.Exec(query, entry.Title, entry.Content, entry.ReadingTime, entry.ID, entry.UserID); err != nil {
+ return fmt.Errorf(`store: unable to update entry #%d: %v`, entry.ID, err)
}
- return tx.Commit()
+ return nil
}
// createEntry add a new entry.
diff --git a/internal/tests/entry_test.go b/internal/tests/entry_test.go
index dde5582a..b0669797 100644
--- a/internal/tests/entry_test.go
+++ b/internal/tests/entry_test.go
@@ -397,6 +397,40 @@ func TestUpdateStatus(t *testing.T) {
}
}
+func TestUpdateEntry(t *testing.T) {
+ client := createClient(t)
+ createFeed(t, client)
+
+ result, err := client.Entries(&miniflux.Filter{Limit: 1})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ title := "New title"
+ content := "New content"
+
+ _, err = client.UpdateEntry(result.Entries[0].ID, &miniflux.EntryModificationRequest{
+ Title: &title,
+ Content: &content,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ entry, err := client.Entry(result.Entries[0].ID)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if entry.Title != title {
+ t.Fatal("The entry title should be updated")
+ }
+
+ if entry.Content != content {
+ t.Fatal("The entry content should be updated")
+ }
+}
+
func TestToggleBookmark(t *testing.T) {
client := createClient(t)
createFeed(t, client)
diff --git a/internal/ui/entry_scraper.go b/internal/ui/entry_scraper.go
index afbaf061..ad442b16 100644
--- a/internal/ui/entry_scraper.go
+++ b/internal/ui/entry_scraper.go
@@ -58,7 +58,7 @@ func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
return
}
- if err := h.store.UpdateEntryContent(entry); err != nil {
+ if err := h.store.UpdateEntryTitleAndContent(entry); err != nil {
json.ServerError(w, r, err)
return
}
diff --git a/internal/validator/entry.go b/internal/validator/entry.go
index dd7216a0..f4fdb116 100644
--- a/internal/validator/entry.go
+++ b/internal/validator/entry.go
@@ -12,7 +12,7 @@ import (
// ValidateEntriesStatusUpdateRequest validates a status update for a list of entries.
func ValidateEntriesStatusUpdateRequest(request *model.EntriesStatusUpdateRequest) error {
if len(request.EntryIDs) == 0 {
- return fmt.Errorf(`The list of entries cannot be empty`)
+ return fmt.Errorf(`the list of entries cannot be empty`)
}
return ValidateEntryStatus(request.Status)
@@ -25,7 +25,7 @@ func ValidateEntryStatus(status string) error {
return nil
}
- return fmt.Errorf(`Invalid entry status, valid status values are: "%s", "%s" and "%s"`, model.EntryStatusRead, model.EntryStatusUnread, model.EntryStatusRemoved)
+ return fmt.Errorf(`invalid entry status, valid status values are: "%s", "%s" and "%s"`, model.EntryStatusRead, model.EntryStatusUnread, model.EntryStatusRemoved)
}
// ValidateEntryOrder makes sure the sorting order is valid.
@@ -35,5 +35,18 @@ func ValidateEntryOrder(order string) error {
return nil
}
- return fmt.Errorf(`Invalid entry order, valid order values are: "id", "status", "changed_at", "published_at", "created_at", "category_title", "category_id", "title", "author"`)
+ return fmt.Errorf(`invalid entry order, valid order values are: "id", "status", "changed_at", "published_at", "created_at", "category_title", "category_id", "title", "author"`)
+}
+
+// ValidateEntryModification makes sure the entry modification is valid.
+func ValidateEntryModification(request *model.EntryUpdateRequest) error {
+ if request.Title != nil && *request.Title == "" {
+ return fmt.Errorf(`the entry title cannot be empty`)
+ }
+
+ if request.Content != nil && *request.Content == "" {
+ return fmt.Errorf(`the entry content cannot be empty`)
+ }
+
+ return nil
}