When returning HTTP errors, send toasts if request is HTMX

This commit is contained in:
Alessio 2024-08-19 14:43:28 -07:00
parent f8988abef1
commit 5d0fd63591
11 changed files with 47 additions and 36 deletions

View File

@ -15,7 +15,7 @@ func (app *Application) Bookmarks(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Has("scrape") { if r.URL.Query().Has("scrape") {
if app.IsScrapingDisabled { if app.IsScrapingDisabled {
app.InfoLog.Printf("Would have scraped: %s", r.URL.Path) app.InfoLog.Printf("Would have scraped: %s", r.URL.Path)
http.Error(w, "Scraping is disabled (are you logged in?)", 401) http.Error(w, "Scraping is disabled (are you logged in?)", 401) // TODO: toast
return return
} }
@ -33,7 +33,7 @@ func (app *Application) Bookmarks(w http.ResponseWriter, r *http.Request) {
c := persistence.NewUserFeedBookmarksCursor(app.ActiveUser.Handle) c := persistence.NewUserFeedBookmarksCursor(app.ActiveUser.Handle)
err := parse_cursor_value(&c, r) err := parse_cursor_value(&c, r)
if err != nil { if err != nil {
app.error_400_with_message(w, "invalid cursor (must be a number)") app.error_400_with_message(w, r, "invalid cursor (must be a number)")
return return
} }

View File

@ -17,12 +17,12 @@ func (app *Application) UserFollow(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(parts) != 2 { if len(parts) != 2 {
app.error_400_with_message(w, "Bad URL: "+r.URL.Path) app.error_400_with_message(w, r, "Bad URL: "+r.URL.Path)
return return
} }
user, err := app.Profile.GetUserByHandle(scraper.UserHandle(parts[1])) user, err := app.Profile.GetUserByHandle(scraper.UserHandle(parts[1]))
if err != nil { if err != nil {
app.error_404(w) app.error_404(w, r)
return return
} }
@ -41,12 +41,12 @@ func (app *Application) UserUnfollow(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(parts) != 2 { if len(parts) != 2 {
app.error_400_with_message(w, "Bad URL: "+r.URL.Path) app.error_400_with_message(w, r, "Bad URL: "+r.URL.Path)
return return
} }
user, err := app.Profile.GetUserByHandle(scraper.UserHandle(parts[1])) user, err := app.Profile.GetUserByHandle(scraper.UserHandle(parts[1]))
if err != nil { if err != nil {
app.error_404(w) app.error_404(w, r)
return return
} }

View File

@ -39,7 +39,7 @@ func (app *Application) ListDetailFeed(w http.ResponseWriter, r *http.Request) {
c := persistence.NewListCursor(list.ID) c := persistence.NewListCursor(list.ID)
err := parse_cursor_value(&c, r) err := parse_cursor_value(&c, r)
if err != nil { if err != nil {
app.error_400_with_message(w, "invalid cursor (must be a number)") app.error_400_with_message(w, r, "invalid cursor (must be a number)")
return return
} }
feed, err := app.Profile.NextPage(c, app.ActiveUser.ID) feed, err := app.Profile.NextPage(c, app.ActiveUser.ID)
@ -98,7 +98,7 @@ func (app *Application) ListDetail(w http.ResponseWriter, r *http.Request) {
case "remove_user": case "remove_user":
app.ListRemoveUser(w, r) app.ListRemoveUser(w, r)
default: default:
app.error_404(w) app.error_404(w, r)
} }
} }
@ -109,7 +109,7 @@ func (app *Application) ListAddUser(w http.ResponseWriter, r *http.Request) {
} }
user, err := app.Profile.GetUserByHandle(UserHandle(handle)) user, err := app.Profile.GetUserByHandle(UserHandle(handle))
if err != nil { if err != nil {
app.error_400_with_message(w, "Fetch user: "+err.Error()) app.error_400_with_message(w, r, "Fetch user: "+err.Error())
return return
} }
list := get_list_from_context(r.Context()) list := get_list_from_context(r.Context())
@ -124,7 +124,7 @@ func (app *Application) ListRemoveUser(w http.ResponseWriter, r *http.Request) {
} }
user, err := app.Profile.GetUserByHandle(UserHandle(handle)) user, err := app.Profile.GetUserByHandle(UserHandle(handle))
if err != nil { if err != nil {
app.error_400_with_message(w, "Fetch user: "+err.Error()) app.error_400_with_message(w, r, "Fetch user: "+err.Error())
return return
} }
list := get_list_from_context(r.Context()) list := get_list_from_context(r.Context())
@ -141,12 +141,12 @@ func (app *Application) Lists(w http.ResponseWriter, r *http.Request) {
if parts[0] != "" { // If there's an ID param if parts[0] != "" { // If there's an ID param
_list_id, err := strconv.Atoi(parts[0]) _list_id, err := strconv.Atoi(parts[0])
if err != nil { if err != nil {
app.error_400_with_message(w, "List ID must be a number") app.error_400_with_message(w, r, "List ID must be a number")
return return
} }
list, err := app.Profile.GetListById(ListID(_list_id)) list, err := app.Profile.GetListById(ListID(_list_id))
if err != nil { if err != nil {
app.error_404(w) app.error_404(w, r)
return return
} }
req_with_ctx := r.WithContext(add_list_to_context(r.Context(), list)) req_with_ctx := r.WithContext(add_list_to_context(r.Context(), list))

View File

@ -71,7 +71,7 @@ func (app *Application) after_login(w http.ResponseWriter, r *http.Request, api
// Ensure the user is downloaded // Ensure the user is downloaded
user, err := scraper.GetUser(api.UserHandle) user, err := scraper.GetUser(api.UserHandle)
if err != nil { if err != nil {
app.error_404(w) app.error_404(w, r)
return return
} }
panic_if(app.Profile.SaveUser(&user)) panic_if(app.Profile.SaveUser(&user))
@ -115,7 +115,7 @@ func (app *Application) ChangeSession(w http.ResponseWriter, r *http.Request) {
panic_if(json.Unmarshal(formdata, &form)) // TODO: HTTP 400 not 500 panic_if(json.Unmarshal(formdata, &form)) // TODO: HTTP 400 not 500
err = app.SetActiveUser(scraper.UserHandle(form.AccountName)) err = app.SetActiveUser(scraper.UserHandle(form.AccountName))
if err != nil { if err != nil {
app.error_400_with_message(w, fmt.Sprintf("User not in database: %s", form.AccountName)) app.error_400_with_message(w, r, fmt.Sprintf("User not in database: %s", form.AccountName))
return return
} }
data := Notifications{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} data := Notifications{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))}

View File

@ -202,7 +202,7 @@ func (app *Application) Messages(w http.ResponseWriter, r *http.Request) {
app.traceLog.Printf("'Messages' handler (path: %q)", r.URL.Path) app.traceLog.Printf("'Messages' handler (path: %q)", r.URL.Path)
if app.ActiveUser.ID == 0 { if app.ActiveUser.ID == 0 {
app.error_401(w) app.error_401(w, r)
return return
} }

View File

@ -50,7 +50,7 @@ func (app *Application) Search(w http.ResponseWriter, r *http.Request) {
// Redirect GET param "q" to use a URL param instead // Redirect GET param "q" to use a URL param instead
search_text = r.URL.Query().Get("q") search_text = r.URL.Query().Get("q")
if search_text == "" { if search_text == "" {
app.error_400_with_message(w, "Empty search query") app.error_400_with_message(w, r, "Empty search query")
return return
// TODO: return an actual page // TODO: return an actual page
} }
@ -114,19 +114,19 @@ func (app *Application) Search(w http.ResponseWriter, r *http.Request) {
c, err := persistence.NewCursorFromSearchQuery(search_text) c, err := persistence.NewCursorFromSearchQuery(search_text)
if err != nil { if err != nil {
app.error_400_with_message(w, err.Error()) app.error_400_with_message(w, r, err.Error())
return return
// TODO: return actual page // TODO: return actual page
} }
err = parse_cursor_value(&c, r) err = parse_cursor_value(&c, r)
if err != nil { if err != nil {
app.error_400_with_message(w, "invalid cursor (must be a number)") app.error_400_with_message(w, r, "invalid cursor (must be a number)")
return return
} }
var is_ok bool var is_ok bool
c.SortOrder, is_ok = persistence.SortOrderFromString(r.URL.Query().Get("sort-order")) c.SortOrder, is_ok = persistence.SortOrderFromString(r.URL.Query().Get("sort-order"))
if !is_ok && r.URL.Query().Get("sort-order") != "" { if !is_ok && r.URL.Query().Get("sort-order") != "" {
app.error_400_with_message(w, "Invalid sort order") app.error_400_with_message(w, r, "Invalid sort order")
} }
feed, err := app.Profile.NextPage(c, app.ActiveUser.ID) feed, err := app.Profile.NextPage(c, app.ActiveUser.ID)

View File

@ -9,7 +9,7 @@ func (app *Application) NavSidebarPollUpdates(w http.ResponseWriter, r *http.Req
// Must be an HTMX request, otherwise HTTP 400 // Must be an HTMX request, otherwise HTTP 400
if !is_htmx(r) { if !is_htmx(r) {
app.error_400_with_message(w, "This is an HTMX-only endpoint, not a page") app.error_400_with_message(w, r, "This is an HTMX-only endpoint, not a page")
return return
} }

View File

@ -20,7 +20,7 @@ func (app *Application) OfflineTimeline(w http.ResponseWriter, r *http.Request)
c := persistence.NewTimelineCursor() c := persistence.NewTimelineCursor()
err := parse_cursor_value(&c, r) err := parse_cursor_value(&c, r)
if err != nil { if err != nil {
app.error_400_with_message(w, "invalid cursor (must be a number)") app.error_400_with_message(w, r, "invalid cursor (must be a number)")
return return
} }
@ -65,7 +65,7 @@ func (app *Application) Timeline(w http.ResponseWriter, r *http.Request) {
} }
err := parse_cursor_value(&c, r) err := parse_cursor_value(&c, r)
if err != nil { if err != nil {
app.error_400_with_message(w, "invalid cursor (must be a number)") app.error_400_with_message(w, r, "invalid cursor (must be a number)")
return return
} }

View File

@ -106,7 +106,7 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
val, err := strconv.Atoi(parts[1]) val, err := strconv.Atoi(parts[1])
if err != nil { if err != nil {
app.error_400_with_message(w, fmt.Sprintf("Invalid tweet ID: %q", parts[1])) app.error_400_with_message(w, r, fmt.Sprintf("Invalid tweet ID: %q", parts[1]))
return return
} }
tweet_id := scraper.TweetID(val) tweet_id := scraper.TweetID(val)
@ -123,7 +123,7 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
app.ErrorLog.Print(fmt.Errorf("TweetDetail (%d): %w", tweet_id, err)) app.ErrorLog.Print(fmt.Errorf("TweetDetail (%d): %w", tweet_id, err))
if errors.Is(err, ErrNotFound) { if errors.Is(err, ErrNotFound) {
// Can't find the tweet; abort // Can't find the tweet; abort
app.toast(w, r, Toast{Title: "Not found", Message: "Tweet not found in database", Type: "error"}) app.error_404(w, r)
return return
} else if errors.Is(err, scraper.ErrSessionInvalidated) { } else if errors.Is(err, scraper.ErrSessionInvalidated) {
toasts = append(toasts, Toast{ toasts = append(toasts, Toast{

View File

@ -21,7 +21,7 @@ func (app *Application) UserFeed(w http.ResponseWriter, r *http.Request) {
user, err = scraper.GetUser(scraper.UserHandle(parts[0])) user, err = scraper.GetUser(scraper.UserHandle(parts[0]))
} }
if err != nil { if err != nil {
app.error_404(w) app.error_404(w, r)
return return
} }
panic_if(app.Profile.SaveUser(&user)) panic_if(app.Profile.SaveUser(&user))
@ -78,7 +78,7 @@ func (app *Application) UserFeed(w http.ResponseWriter, r *http.Request) {
} }
err = parse_cursor_value(&c, r) err = parse_cursor_value(&c, r)
if err != nil { if err != nil {
app.error_400_with_message(w, "invalid cursor (must be a number)") app.error_400_with_message(w, r, "invalid cursor (must be a number)")
return return
} }

View File

@ -12,21 +12,32 @@ func panic_if(err error) {
} }
} }
// func (app *Application) error_400(w http.ResponseWriter) { func (app *Application) error_400_with_message(w http.ResponseWriter, r *http.Request, msg string) {
// http.Error(w, "Bad Request", 400) if is_htmx(r) {
// } w.WriteHeader(400)
app.toast(w, r, Toast{Title: "Bad Request", Message: msg, Type: "error"})
func (app *Application) error_400_with_message(w http.ResponseWriter, msg string) { } else {
http.Error(w, fmt.Sprintf("Bad Request\n\n%s", msg), 400) http.Error(w, fmt.Sprintf("Bad Request\n\n%s", msg), 400)
} }
func (app *Application) error_401(w http.ResponseWriter) {
http.Error(w, "Please log in or set an active session", 401)
} }
func (app *Application) error_404(w http.ResponseWriter) { func (app *Application) error_401(w http.ResponseWriter, r *http.Request) {
if is_htmx(r) {
w.WriteHeader(401)
app.toast(w, r, Toast{Title: "Login required", Message: "Please log in or set an active session", Type: "error"})
} else {
http.Error(w, "Please log in or set an active session", 401)
}
}
func (app *Application) error_404(w http.ResponseWriter, r *http.Request) {
if is_htmx(r) {
w.WriteHeader(404)
app.toast(w, r, Toast{Title: "Not found", Type: "error"})
} else {
http.Error(w, "Not Found", 404) http.Error(w, "Not Found", 404)
} }
}
func (app *Application) error_500(w http.ResponseWriter, r *http.Request, err error) { func (app *Application) error_500(w http.ResponseWriter, r *http.Request, err error) {
trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack()) trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())