From 79c91d71c8e817bcfd9d5c7e06436524115bda04 Mon Sep 17 00:00:00 2001 From: njzy Date: Tue, 8 Aug 2023 14:12:41 +0000 Subject: [PATCH] feat: support force refresh in feed edit and feed entries page --- api/feed.go | 2 +- cli/refresh_feeds.go | 2 +- http/request/params.go | 16 +++++++++++++ locale/translations/de_DE.json | 1 + locale/translations/el_EL.json | 1 + locale/translations/en_US.json | 1 + locale/translations/es_ES.json | 1 + locale/translations/fi_FI.json | 1 + locale/translations/fr_FR.json | 1 + locale/translations/hi_IN.json | 1 + locale/translations/id_ID.json | 1 + locale/translations/it_IT.json | 1 + locale/translations/ja_JP.json | 1 + locale/translations/nl_NL.json | 1 + locale/translations/pl_PL.json | 1 + locale/translations/pt_BR.json | 1 + locale/translations/ru_RU.json | 1 + locale/translations/tr_TR.json | 1 + locale/translations/uk_UA.json | 1 + locale/translations/zh_CN.json | 1 + locale/translations/zh_TW.json | 1 + reader/handler/handler.go | 11 +++++---- reader/processor/processor.go | 4 ++-- template/templates/views/edit_feed.html | 9 ++++++- template/templates/views/feed_entries.html | 9 ++++++- ui/feed_refresh.go | 3 ++- ui/static/js/app.js | 28 +++++++++++++++------- ui/static/js/bootstrap.js | 4 +++- ui/ui.go | 3 ++- worker/worker.go | 2 +- 30 files changed, 88 insertions(+), 23 deletions(-) diff --git a/api/feed.go b/api/feed.go index 339557c5..12cbf919 100644 --- a/api/feed.go +++ b/api/feed.go @@ -47,7 +47,7 @@ func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) { return } - err := feedHandler.RefreshFeed(h.store, userID, feedID) + err := feedHandler.RefreshFeed(h.store, userID, feedID, false) if err != nil { json.ServerError(w, r, err) return diff --git a/cli/refresh_feeds.go b/cli/refresh_feeds.go index 355c8287..63ef9efb 100644 --- a/cli/refresh_feeds.go +++ b/cli/refresh_feeds.go @@ -34,7 +34,7 @@ func refreshFeeds(store *storage.Storage) { defer wg.Done() for job := range jobQueue { logger.Info("[Cronjob] Refreshing feed #%d for user #%d in worker #%d", job.FeedID, job.UserID, workerID) - if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID); err != nil { + if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID, false); err != nil { logger.Error("[Cronjob] Refreshing the feed #%d returned this error: %v", job.FeedID, err) } } diff --git a/http/request/params.go b/http/request/params.go index dc7512b6..cd0eff38 100644 --- a/http/request/params.go +++ b/http/request/params.go @@ -107,6 +107,22 @@ func QueryInt64Param(r *http.Request, param string, defaultValue int64) int64 { return val } +// QueryBoolParam returns a query string parameter as bool. +func QueryBoolParam(r *http.Request, param string, defaultValue bool) bool { + value := r.URL.Query().Get(param) + if value == "" { + return defaultValue + } + + val, err := strconv.ParseBool(value) + + if err != nil { + return defaultValue + } + + return val +} + // HasQueryParam checks if the query string contains the given parameter. func HasQueryParam(r *http.Request, param string) bool { values := r.URL.Query() diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index dce26e84..d06830b8 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -1,5 +1,6 @@ { "confirm.question": "Sind Sie sicher?", + "confirm.question.refresh": "Möchten Sie eine erzwungene Aktualisierung durchführen?", "confirm.yes": "ja", "confirm.no": "nein", "confirm.loading": "In Arbeit...", diff --git a/locale/translations/el_EL.json b/locale/translations/el_EL.json index d504e409..e7774136 100644 --- a/locale/translations/el_EL.json +++ b/locale/translations/el_EL.json @@ -1,5 +1,6 @@ { "confirm.question": "Είστε σίγουροι;", + "confirm.question.refresh": "Θέλετε να επιτελέσετε μια υποχρεωτική ανανέωση;", "confirm.yes": "ναι", "confirm.no": "όχι", "confirm.loading": "Σε εξέλιξη...", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index 7c144379..7b3e2ccf 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -1,5 +1,6 @@ { "confirm.question": "Are you sure?", + "confirm.question.refresh": "Are you want to force refresh?", "confirm.yes": "yes", "confirm.no": "no", "confirm.loading": "In progress…", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index 8653aca0..d09be909 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -1,5 +1,6 @@ { "confirm.question": "¿Estás seguro?", + "confirm.question.refresh": "¿Quieres forzar la actualización?", "confirm.yes": "sí", "confirm.no": "no", "confirm.loading": "En progreso...", diff --git a/locale/translations/fi_FI.json b/locale/translations/fi_FI.json index b934bc4b..81fdb5e6 100644 --- a/locale/translations/fi_FI.json +++ b/locale/translations/fi_FI.json @@ -1,5 +1,6 @@ { "confirm.question": "Oletko varma?", + "confirm.question.refresh": "Haluatko pakottaa päivityksen?", "confirm.yes": "kyllä", "confirm.no": "ei", "confirm.loading": "Käynnissä...", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index 43d181ab..cc024e18 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -1,5 +1,6 @@ { "confirm.question": "Êtes-vous sûr ?", + "confirm.question.refresh": "Voulez-vous forcer le rafraîchissement ?", "confirm.yes": "oui", "confirm.no": "non", "confirm.loading": "En cours...", diff --git a/locale/translations/hi_IN.json b/locale/translations/hi_IN.json index 27ab00b0..c56b275d 100644 --- a/locale/translations/hi_IN.json +++ b/locale/translations/hi_IN.json @@ -1,5 +1,6 @@ { "confirm.question": "मंजूर है?", + "confirm.question.refresh": "क्या आप बल द्वारा ताज़ा करना चाहते हैं?", "confirm.yes": "हाँ", "confirm.no": " नहीं", "confirm.loading": " प्रगति में है ...", diff --git a/locale/translations/id_ID.json b/locale/translations/id_ID.json index 7b62a2a7..f0c62fd5 100644 --- a/locale/translations/id_ID.json +++ b/locale/translations/id_ID.json @@ -1,5 +1,6 @@ { "confirm.question": "Apakah Anda yakin?", + "confirm.question.refresh": "Apakah Anda ingin memaksa penyegaran?", "confirm.yes": "ya", "confirm.no": "tidak", "confirm.loading": "Sedang progres...", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index f4031ce8..e45754a7 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -1,5 +1,6 @@ { "confirm.question": "Sei sicuro?", + "confirm.question.refresh": "Vuoi forzare l'aggiornamento?", "confirm.yes": "sì", "confirm.no": "no", "confirm.loading": "In corso...", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 68ae1412..a7e88a40 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -1,5 +1,6 @@ { "confirm.question": "よろしいですか?", + "confirm.question.refresh": "強制的に更新しますか?", "confirm.yes": "はい", "confirm.no": "いいえ", "confirm.loading": "実行中…", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index e74a482e..d401f98d 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -1,5 +1,6 @@ { "confirm.question": "Weet je het zeker?", + "confirm.question.refresh": "Wil je een gedwongen vernieuwing uitvoeren?", "confirm.yes": "ja", "confirm.no": "nee", "confirm.loading": "Bezig...", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index d6147821..21f0c339 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -1,5 +1,6 @@ { "confirm.question": "Czy jesteś pewny?", + "confirm.question.refresh": "Czy chcesz wymusić odświeżenie?", "confirm.yes": "tak", "confirm.no": "nie", "confirm.loading": "W toku...", diff --git a/locale/translations/pt_BR.json b/locale/translations/pt_BR.json index 485034e8..e97089df 100644 --- a/locale/translations/pt_BR.json +++ b/locale/translations/pt_BR.json @@ -1,5 +1,6 @@ { "confirm.question": "Tem certeza?", + "confirm.question.refresh": "Você deseja forçar a atualização?", "confirm.yes": "Sim", "confirm.no": "Não", "confirm.loading": "Carregando...", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index 3c334afe..a679cf81 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -1,5 +1,6 @@ { "confirm.question": "Вы уверены?", + "confirm.question.refresh": "Вы хотите выполнить принудительное обновление?", "confirm.yes": "да", "confirm.no": "нет", "confirm.loading": "В процессе…", diff --git a/locale/translations/tr_TR.json b/locale/translations/tr_TR.json index 88d60326..3fd475a4 100644 --- a/locale/translations/tr_TR.json +++ b/locale/translations/tr_TR.json @@ -1,5 +1,6 @@ { "confirm.question": "Emin misiniz?", + "confirm.question.refresh": "Zorla yenilemek istiyor musunuz?", "confirm.yes": "evet", "confirm.no": "hayır", "confirm.loading": "Devam ediyor...", diff --git a/locale/translations/uk_UA.json b/locale/translations/uk_UA.json index 103c05e1..5ba751d4 100644 --- a/locale/translations/uk_UA.json +++ b/locale/translations/uk_UA.json @@ -1,5 +1,6 @@ { "confirm.question": "Ви впевнені?", + "confirm.question.refresh": "Ви хочете змусити оновити?", "confirm.yes": "так", "confirm.no": "ні", "confirm.loading": "В процесі...", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index 13e3d15b..a107a523 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -1,5 +1,6 @@ { "confirm.question": "您确认吗?", + "confirm.question.refresh": "您是否要强制刷新?", "confirm.yes": "是", "confirm.no": "否", "confirm.loading": "执行中…", diff --git a/locale/translations/zh_TW.json b/locale/translations/zh_TW.json index ce246288..e9c5f285 100644 --- a/locale/translations/zh_TW.json +++ b/locale/translations/zh_TW.json @@ -1,5 +1,6 @@ { "confirm.question": "您確認嗎?", + "confirm.question.refresh": "您想要強制刷新嗎?", "confirm.yes": "是", "confirm.no": "否", "confirm.loading": "執行中…", diff --git a/reader/handler/handler.go b/reader/handler/handler.go index b727ef90..03beee8f 100644 --- a/reader/handler/handler.go +++ b/reader/handler/handler.go @@ -83,7 +83,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model subscription.WithClientResponse(response) subscription.CheckedNow() - processor.ProcessFeedEntries(store, subscription, user) + processor.ProcessFeedEntries(store, subscription, user, true) if storeErr := store.CreateFeed(subscription); storeErr != nil { return nil, storeErr @@ -104,7 +104,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model } // RefreshFeed refreshes a feed. -func RefreshFeed(store *storage.Storage, userID, feedID int64) error { +func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool) error { defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[RefreshFeed] feedID=%d", feedID)) user, storeErr := store.UserByID(userID) if storeErr != nil { @@ -173,10 +173,11 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64) error { } originalFeed.Entries = updatedFeed.Entries - processor.ProcessFeedEntries(store, originalFeed, user) + processor.ProcessFeedEntries(store, originalFeed, user, forceRefresh) - // We don't update existing entries when the crawler is enabled (we crawl only inexisting entries). - if storeErr := store.RefreshFeedEntries(originalFeed.UserID, originalFeed.ID, originalFeed.Entries, !originalFeed.Crawler); storeErr != nil { + // We don't update existing entries when the crawler is enabled (we crawl only inexisting entries). Unless it is forced to refresh + updateExistingEntries := forceRefresh || !originalFeed.Crawler + if storeErr := store.RefreshFeedEntries(originalFeed.UserID, originalFeed.ID, originalFeed.Entries, updateExistingEntries); storeErr != nil { originalFeed.WithError(storeErr.Error()) store.UpdateFeedError(originalFeed) return storeErr diff --git a/reader/processor/processor.go b/reader/processor/processor.go index e720e1cf..181eaa6d 100644 --- a/reader/processor/processor.go +++ b/reader/processor/processor.go @@ -38,7 +38,7 @@ var ( ) // ProcessFeedEntries downloads original web page for entries and apply filters. -func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.User) { +func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.User, forceRefresh bool) { var filteredEntries model.Entries // array used for bulk push @@ -56,7 +56,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us url := getUrlFromEntry(feed, entry) entryIsNew := !store.EntryURLExists(feed.ID, entry.URL) - if feed.Crawler && entryIsNew { + if feed.Crawler && (entryIsNew || forceRefresh) { logger.Debug("[Processor] Crawling entry %q from feed %q", url, feed.FeedURL) startTime := time.Now() diff --git a/template/templates/views/edit_feed.html b/template/templates/views/edit_feed.html index af4a5b2a..12338ce3 100644 --- a/template/templates/views/edit_feed.html +++ b/template/templates/views/edit_feed.html @@ -11,7 +11,14 @@ {{ icon "entries" }}{{ t "menu.feed_entries" }}
  • - {{ icon "refresh" }}{{ t "menu.refresh_feed" }} + {{ icon "refresh" }}{{ t "menu.refresh_feed" }}
  • diff --git a/template/templates/views/feed_entries.html b/template/templates/views/feed_entries.html index 4615e044..999e99c3 100644 --- a/template/templates/views/feed_entries.html +++ b/template/templates/views/feed_entries.html @@ -37,7 +37,14 @@ {{ end }}
  • - {{ icon "refresh" }}{{ t "menu.refresh_feed" }} + {{ icon "refresh" }}{{ t "menu.refresh_feed" }}
  • {{ icon "edit" }}{{ t "menu.edit_feed" }} diff --git a/ui/feed_refresh.go b/ui/feed_refresh.go index e8c5f353..26afbe84 100644 --- a/ui/feed_refresh.go +++ b/ui/feed_refresh.go @@ -15,7 +15,8 @@ import ( func (h *handler) refreshFeed(w http.ResponseWriter, r *http.Request) { feedID := request.RouteInt64Param(r, "feedID") - if err := feedHandler.RefreshFeed(h.store, request.UserID(r), feedID); err != nil { + forceRefresh := request.QueryBoolParam(r, "forceRefresh", false) + if err := feedHandler.RefreshFeed(h.store, request.UserID(r), feedID, forceRefresh); err != nil { logger.Error("[UI:RefreshFeed] %v", err) } diff --git a/ui/static/js/app.js b/ui/static/js/app.js index 9ef95730..a89da8bd 100644 --- a/ui/static/js/app.js +++ b/ui/static/js/app.js @@ -561,18 +561,22 @@ function handleConfirmationMessage(linkElement, callback) { let containerElement = linkElement.parentNode; let questionElement = document.createElement("span"); - let yesElement = document.createElement("a"); - yesElement.href = "#"; - yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes)); - yesElement.onclick = (event) => { - event.preventDefault(); - + function createLoadingElement() { let loadingElement = document.createElement("span"); loadingElement.className = "loading"; loadingElement.appendChild(document.createTextNode(linkElement.dataset.labelLoading)); questionElement.remove(); containerElement.appendChild(loadingElement); + } + + let yesElement = document.createElement("a"); + yesElement.href = "#"; + yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes)); + yesElement.onclick = (event) => { + event.preventDefault(); + + createLoadingElement(); callback(linkElement.dataset.url, linkElement.dataset.redirectUrl); }; @@ -582,8 +586,16 @@ function handleConfirmationMessage(linkElement, callback) { noElement.appendChild(document.createTextNode(linkElement.dataset.labelNo)); noElement.onclick = (event) => { event.preventDefault(); - linkElement.style.display = "inline"; - questionElement.remove(); + + const noActionUrl = linkElement.dataset.noActionUrl; + if (noActionUrl) { + createLoadingElement(); + + callback(noActionUrl, linkElement.dataset.redirectUrl); + } else { + linkElement.style.display = "inline"; + questionElement.remove(); + } }; questionElement.className = "confirm"; diff --git a/ui/static/js/bootstrap.js b/ui/static/js/bootstrap.js index 6cba7089..0cd878ef 100644 --- a/ui/static/js/bootstrap.js +++ b/ui/static/js/bootstrap.js @@ -59,9 +59,11 @@ document.addEventListener("DOMContentLoaded", function () { onClick("a[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => { let request = new RequestBuilder(url); - request.withCallback(() => { + request.withCallback((response) => { if (redirectURL) { window.location.href = redirectURL; + } else if (response && response.redirected && response.url) { + window.location.href = response.url; } else { window.location.reload(); } diff --git a/ui/ui.go b/ui/ui.go index 969bf975..ecdc1800 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -66,7 +66,8 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) { uiRouter.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Name("refreshAllFeeds").Methods(http.MethodGet) // Individual feed pages. - uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods(http.MethodGet) + uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Name("refreshFeed").Methods(http.MethodGet, http.MethodPost) + uiRouter.HandleFunc("/feed/{feedID}/refresh", handler.refreshFeed).Queries("forceRefresh", "{forceRefresh:true|false}").Name("refreshFeed").Methods(http.MethodGet, http.MethodPost) uiRouter.HandleFunc("/feed/{feedID}/edit", handler.showEditFeedPage).Name("editFeed").Methods(http.MethodGet) uiRouter.HandleFunc("/feed/{feedID}/remove", handler.removeFeed).Name("removeFeed").Methods(http.MethodPost) uiRouter.HandleFunc("/feed/{feedID}/update", handler.updateFeed).Name("updateFeed").Methods(http.MethodPost) diff --git a/worker/worker.go b/worker/worker.go index 2b3b473e..4075ffdb 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -29,7 +29,7 @@ func (w *Worker) Run(c chan model.Job) { logger.Debug("[Worker #%d] Received feed #%d for user #%d", w.id, job.FeedID, job.UserID) startTime := time.Now() - refreshErr := feedHandler.RefreshFeed(w.store, job.UserID, job.FeedID) + refreshErr := feedHandler.RefreshFeed(w.store, job.UserID, job.FeedID, false) if config.Opts.HasMetricsCollector() { status := "success"