Pretty error handlingUnverified
c9bc9bc parent: 3fe5441 modified
internal/server/auth_handler.go +9 -9 | @@ -20,7 +20,7 @@ func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) { | ||
| 20 | 20 | func (s *Server) handleAuthStart(w http.ResponseWriter, r *http.Request) { |
| 21 | 21 | handle := strings.TrimPrefix(r.FormValue("handle"), "@") |
| 22 | 22 | if handle == "" { |
| 23 | - http.Error(w, "handle required", http.StatusBadRequest) | |
| 23 | + s.renderError(w, r, http.StatusBadRequest, "Missing handle", "Please enter your handle.") | |
| 24 | 24 | return |
| 25 | 25 | } |
| 26 | 26 | |
| @@ -30,12 +30,12 @@ func (s *Server) handleAuthStart(w http.ResponseWriter, r *http.Request) { | ||
| 30 | 30 | |
| 31 | 31 | did, resolveErr := atproto.ResolveHandle(r.Context(), handle) |
| 32 | 32 | if resolveErr != nil { |
| 33 | - http.Error(w, "could not resolve handle", http.StatusInternalServerError) | |
| 33 | + s.renderError(w, r, http.StatusBadGateway, "Handle not found", "Could not resolve that handle. Please check and try again.") | |
| 34 | 34 | return |
| 35 | 35 | } |
| 36 | 36 | user, createErr := s.dbs.Users.CreateUser(r.Context(), did) |
| 37 | 37 | if createErr != nil { |
| 38 | - http.Error(w, createErr.Error(), http.StatusInternalServerError) | |
| 38 | + s.renderError(w, r, http.StatusInternalServerError, "Sign in failed", "Could not create your account. Please try again.") | |
| 39 | 39 | return |
| 40 | 40 | } |
| 41 | 41 | s.setUserSession(w, user) |
| @@ -56,21 +56,21 @@ func (s *Server) handleAuthCallback(w http.ResponseWriter, r *http.Request) { | ||
| 56 | 56 | |
| 57 | 57 | handle := params.Get("handle") |
| 58 | 58 | if handle == "" { |
| 59 | - http.Error(w, "handle required", http.StatusBadRequest) | |
| 59 | + s.renderError(w, r, http.StatusBadRequest, "Missing handle", "Please enter your handle.") | |
| 60 | 60 | return |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | 63 | did, err := atproto.ResolveHandle(r.Context(), handle) |
| 64 | 64 | if err != nil { |
| 65 | 65 | s.logger.Error("failed to resolve handle", "error", err) |
| 66 | - http.Error(w, err.Error(), http.StatusInternalServerError) | |
| 66 | + s.renderError(w, r, http.StatusBadGateway, "Handle not found", "Could not resolve that handle. Please check and try again.") | |
| 67 | 67 | return |
| 68 | 68 | } |
| 69 | 69 | |
| 70 | 70 | user, err := s.dbs.Users.CreateUser(r.Context(), did) |
| 71 | 71 | if err != nil { |
| 72 | 72 | s.logger.Error("failed to create user", "error", err) |
| 73 | - http.Error(w, err.Error(), http.StatusInternalServerError) | |
| 73 | + s.renderError(w, r, http.StatusInternalServerError, "Sign in failed", "Could not create your account. Please try again.") | |
| 74 | 74 | return |
| 75 | 75 | } |
| 76 | 76 | |
| @@ -82,7 +82,7 @@ func (s *Server) handleOAuthCallback(w http.ResponseWriter, r *http.Request) { | ||
| 82 | 82 | sessData, err := s.oauth.ProcessCallback(r.Context(), r.URL.Query()) |
| 83 | 83 | if err != nil { |
| 84 | 84 | s.logger.Error("OAuth callback failed", "error", err) |
| 85 | - http.Error(w, "authentication failed: "+err.Error(), http.StatusInternalServerError) | |
| 85 | + s.renderError(w, r, http.StatusInternalServerError, "Authentication failed", "Something went wrong during sign in. Please try again.") | |
| 86 | 86 | return |
| 87 | 87 | } |
| 88 | 88 | |
| @@ -93,7 +93,7 @@ func (s *Server) handleOAuthCallback(w http.ResponseWriter, r *http.Request) { | ||
| 93 | 93 | user, err := s.dbs.Users.CreateUser(r.Context(), did) |
| 94 | 94 | if err != nil { |
| 95 | 95 | s.logger.Error("failed to create user", "error", err) |
| 96 | - http.Error(w, err.Error(), http.StatusInternalServerError) | |
| 96 | + s.renderError(w, r, http.StatusInternalServerError, "Sign in failed", "Could not create your account. Please try again.") | |
| 97 | 97 | return |
| 98 | 98 | } |
| 99 | 99 | |
| @@ -105,7 +105,7 @@ func (s *Server) handleOAuthCallback(w http.ResponseWriter, r *http.Request) { | ||
| 105 | 105 | encoded, err := encodeSession(sessionData) |
| 106 | 106 | if err != nil { |
| 107 | 107 | s.logger.Error("failed to encode session", "error", err) |
| 108 | - http.Error(w, "internal error", http.StatusInternalServerError) | |
| 108 | + s.renderError(w, r, http.StatusInternalServerError, "Session error", "Could not create your session. Please try again.") | |
| 109 | 109 | return |
| 110 | 110 | } |
| 111 | 111 | |
| @@ -20,7 +20,7 @@ func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) { | |||
| 20 | func (s *Server) handleAuthStart(w http.ResponseWriter, r *http.Request) { | 20 | func (s *Server) handleAuthStart(w http.ResponseWriter, r *http.Request) { |
| 21 | handle := strings.TrimPrefix(r.FormValue("handle"), "@") | 21 | handle := strings.TrimPrefix(r.FormValue("handle"), "@") |
| 22 | if handle == "" { | 22 | if handle == "" { |
| 23 | - http.Error(w, "handle required", http.StatusBadRequest) | 23 | + s.renderError(w, r, http.StatusBadRequest, "Missing handle", "Please enter your handle.") |
| 24 | return | 24 | return |
| 25 | } | 25 | } |
| 26 | 26 | ||
| @@ -30,12 +30,12 @@ func (s *Server) handleAuthStart(w http.ResponseWriter, r *http.Request) { | |||
| 30 | 30 | ||
| 31 | did, resolveErr := atproto.ResolveHandle(r.Context(), handle) | 31 | did, resolveErr := atproto.ResolveHandle(r.Context(), handle) |
| 32 | if resolveErr != nil { | 32 | if resolveErr != nil { |
| 33 | - http.Error(w, "could not resolve handle", http.StatusInternalServerError) | 33 | + s.renderError(w, r, http.StatusBadGateway, "Handle not found", "Could not resolve that handle. Please check and try again.") |
| 34 | return | 34 | return |
| 35 | } | 35 | } |
| 36 | user, createErr := s.dbs.Users.CreateUser(r.Context(), did) | 36 | user, createErr := s.dbs.Users.CreateUser(r.Context(), did) |
| 37 | if createErr != nil { | 37 | if createErr != nil { |
| 38 | - http.Error(w, createErr.Error(), http.StatusInternalServerError) | 38 | + s.renderError(w, r, http.StatusInternalServerError, "Sign in failed", "Could not create your account. Please try again.") |
| 39 | return | 39 | return |
| 40 | } | 40 | } |
| 41 | s.setUserSession(w, user) | 41 | s.setUserSession(w, user) |
| @@ -56,21 +56,21 @@ func (s *Server) handleAuthCallback(w http.ResponseWriter, r *http.Request) { | |||
| 56 | 56 | ||
| 57 | handle := params.Get("handle") | 57 | handle := params.Get("handle") |
| 58 | if handle == "" { | 58 | if handle == "" { |
| 59 | - http.Error(w, "handle required", http.StatusBadRequest) | 59 | + s.renderError(w, r, http.StatusBadRequest, "Missing handle", "Please enter your handle.") |
| 60 | return | 60 | return |
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | did, err := atproto.ResolveHandle(r.Context(), handle) | 63 | did, err := atproto.ResolveHandle(r.Context(), handle) |
| 64 | if err != nil { | 64 | if err != nil { |
| 65 | s.logger.Error("failed to resolve handle", "error", err) | 65 | s.logger.Error("failed to resolve handle", "error", err) |
| 66 | - http.Error(w, err.Error(), http.StatusInternalServerError) | 66 | + s.renderError(w, r, http.StatusBadGateway, "Handle not found", "Could not resolve that handle. Please check and try again.") |
| 67 | return | 67 | return |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | user, err := s.dbs.Users.CreateUser(r.Context(), did) | 70 | user, err := s.dbs.Users.CreateUser(r.Context(), did) |
| 71 | if err != nil { | 71 | if err != nil { |
| 72 | s.logger.Error("failed to create user", "error", err) | 72 | s.logger.Error("failed to create user", "error", err) |
| 73 | - http.Error(w, err.Error(), http.StatusInternalServerError) | 73 | + s.renderError(w, r, http.StatusInternalServerError, "Sign in failed", "Could not create your account. Please try again.") |
| 74 | return | 74 | return |
| 75 | } | 75 | } |
| 76 | 76 | ||
| @@ -82,7 +82,7 @@ func (s *Server) handleOAuthCallback(w http.ResponseWriter, r *http.Request) { | |||
| 82 | sessData, err := s.oauth.ProcessCallback(r.Context(), r.URL.Query()) | 82 | sessData, err := s.oauth.ProcessCallback(r.Context(), r.URL.Query()) |
| 83 | if err != nil { | 83 | if err != nil { |
| 84 | s.logger.Error("OAuth callback failed", "error", err) | 84 | s.logger.Error("OAuth callback failed", "error", err) |
| 85 | - http.Error(w, "authentication failed: "+err.Error(), http.StatusInternalServerError) | 85 | + s.renderError(w, r, http.StatusInternalServerError, "Authentication failed", "Something went wrong during sign in. Please try again.") |
| 86 | return | 86 | return |
| 87 | } | 87 | } |
| 88 | 88 | ||
| @@ -93,7 +93,7 @@ func (s *Server) handleOAuthCallback(w http.ResponseWriter, r *http.Request) { | |||
| 93 | user, err := s.dbs.Users.CreateUser(r.Context(), did) | 93 | user, err := s.dbs.Users.CreateUser(r.Context(), did) |
| 94 | if err != nil { | 94 | if err != nil { |
| 95 | s.logger.Error("failed to create user", "error", err) | 95 | s.logger.Error("failed to create user", "error", err) |
| 96 | - http.Error(w, err.Error(), http.StatusInternalServerError) | 96 | + s.renderError(w, r, http.StatusInternalServerError, "Sign in failed", "Could not create your account. Please try again.") |
| 97 | return | 97 | return |
| 98 | } | 98 | } |
| 99 | 99 | ||
| @@ -105,7 +105,7 @@ func (s *Server) handleOAuthCallback(w http.ResponseWriter, r *http.Request) { | |||
| 105 | encoded, err := encodeSession(sessionData) | 105 | encoded, err := encodeSession(sessionData) |
| 106 | if err != nil { | 106 | if err != nil { |
| 107 | s.logger.Error("failed to encode session", "error", err) | 107 | s.logger.Error("failed to encode session", "error", err) |
| 108 | - http.Error(w, "internal error", http.StatusInternalServerError) | 108 | + s.renderError(w, r, http.StatusInternalServerError, "Session error", "Could not create your session. Please try again.") |
| 109 | return | 109 | return |
| 110 | } | 110 | } |
| 111 | 111 | ||
modified
internal/server/profile_handler.go +2 -2 | @@ -20,7 +20,7 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) { | ||
| 20 | 20 | resolved, err := atproto.ResolveHandle(ctx, param) |
| 21 | 21 | if err != nil { |
| 22 | 22 | s.logger.Warn("failed to resolve handle", "error", err, "handle", param) |
| 23 | - http.Error(w, "handle not found", http.StatusNotFound) | |
| 23 | + s.renderError(w, r, http.StatusNotFound, "Handle not found", "Could not find a user with that handle.") | |
| 24 | 24 | return |
| 25 | 25 | } |
| 26 | 26 | did = resolved |
| @@ -29,7 +29,7 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) { | ||
| 29 | 29 | profileUser, err := s.dbs.Users.GetUser(ctx, did) |
| 30 | 30 | if err != nil { |
| 31 | 31 | s.logger.Warn("failed to get user", "error", err, "did", did) |
| 32 | - http.Error(w, "user not found", http.StatusNotFound) | |
| 32 | + s.renderError(w, r, http.StatusNotFound, "User not found", "This user doesn't exist in Glean yet.") | |
| 33 | 33 | return |
| 34 | 34 | } |
| 35 | 35 | |
| @@ -20,7 +20,7 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) { | |||
| 20 | resolved, err := atproto.ResolveHandle(ctx, param) | 20 | resolved, err := atproto.ResolveHandle(ctx, param) |
| 21 | if err != nil { | 21 | if err != nil { |
| 22 | s.logger.Warn("failed to resolve handle", "error", err, "handle", param) | 22 | s.logger.Warn("failed to resolve handle", "error", err, "handle", param) |
| 23 | - http.Error(w, "handle not found", http.StatusNotFound) | 23 | + s.renderError(w, r, http.StatusNotFound, "Handle not found", "Could not find a user with that handle.") |
| 24 | return | 24 | return |
| 25 | } | 25 | } |
| 26 | did = resolved | 26 | did = resolved |
| @@ -29,7 +29,7 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) { | |||
| 29 | profileUser, err := s.dbs.Users.GetUser(ctx, did) | 29 | profileUser, err := s.dbs.Users.GetUser(ctx, did) |
| 30 | if err != nil { | 30 | if err != nil { |
| 31 | s.logger.Warn("failed to get user", "error", err, "did", did) | 31 | s.logger.Warn("failed to get user", "error", err, "did", did) |
| 32 | - http.Error(w, "user not found", http.StatusNotFound) | 32 | + s.renderError(w, r, http.StatusNotFound, "User not found", "This user doesn't exist in Glean yet.") |
| 33 | return | 33 | return |
| 34 | } | 34 | } |
| 35 | 35 | ||
modified
internal/server/server.go +13 -0 | @@ -512,6 +512,19 @@ func (s *Server) handleNotFound(w http.ResponseWriter, r *http.Request) { | ||
| 512 | 512 | s.render(w, r, "404.html", nil) |
| 513 | 513 | } |
| 514 | 514 | |
| 515 | +func (s *Server) renderError(w http.ResponseWriter, r *http.Request, code int, title, message string) { | |
| 516 | + if r.Header.Get("HX-Request") == "true" { | |
| 517 | + w.WriteHeader(code) | |
| 518 | + w.Write([]byte(message)) | |
| 519 | + return | |
| 520 | + } | |
| 521 | + w.WriteHeader(code) | |
| 522 | + s.render(w, r, "error.html", map[string]any{ | |
| 523 | + "Title": title, | |
| 524 | + "Message": message, | |
| 525 | + }) | |
| 526 | +} | |
| 527 | + | |
| 515 | 528 | func (s *Server) render(w http.ResponseWriter, r *http.Request, name string, data map[string]any) { |
| 516 | 529 | if data == nil { |
| 517 | 530 | data = map[string]any{} |
| @@ -512,6 +512,19 @@ func (s *Server) handleNotFound(w http.ResponseWriter, r *http.Request) { | |||
| 512 | s.render(w, r, "404.html", nil) | 512 | s.render(w, r, "404.html", nil) |
| 513 | } | 513 | } |
| 514 | 514 | ||
| 515 | +func (s *Server) renderError(w http.ResponseWriter, r *http.Request, code int, title, message string) { | ||
| 516 | + if r.Header.Get("HX-Request") == "true" { | ||
| 517 | + w.WriteHeader(code) | ||
| 518 | + w.Write([]byte(message)) | ||
| 519 | + return | ||
| 520 | + } | ||
| 521 | + w.WriteHeader(code) | ||
| 522 | + s.render(w, r, "error.html", map[string]any{ | ||
| 523 | + "Title": title, | ||
| 524 | + "Message": message, | ||
| 525 | + }) | ||
| 526 | +} | ||
| 527 | + | ||
| 515 | func (s *Server) render(w http.ResponseWriter, r *http.Request, name string, data map[string]any) { | 528 | func (s *Server) render(w http.ResponseWriter, r *http.Request, name string, data map[string]any) { |
| 516 | if data == nil { | 529 | if data == nil { |
| 517 | data = map[string]any{} | 530 | data = map[string]any{} |
added
internal/tmpl/error.html +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +{{define "error.html"}} | |
| 2 | +<div class="max-w-md mx-auto mt-20"> | |
| 3 | + <div class="bg-spot-surface rounded-xl shadow-spot-heavy p-8"> | |
| 4 | + <div class="flex justify-center mb-6"> | |
| 5 | + <span class="w-14 h-14">{{template "logo-icon"}}</span> | |
| 6 | + </div> | |
| 7 | + <h1 class="text-2xl font-bold text-spot-text mb-2 text-center">{{.Title}}</h1> | |
| 8 | + <p class="text-spot-secondary text-sm mb-6 text-center">{{.Message}}</p> | |
| 9 | + <a href="/dashboard" class="w-full flex items-center justify-center gap-2 bg-spot-green text-white rounded-pill px-4 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition"> | |
| 10 | + Back to Dashboard | |
| 11 | + </a> | |
| 12 | + </div> | |
| 13 | +</div> | |
| 14 | +{{end}} | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +{{define "error.html"}} | ||
| 2 | +<div class="max-w-md mx-auto mt-20"> | ||
| 3 | + <div class="bg-spot-surface rounded-xl shadow-spot-heavy p-8"> | ||
| 4 | + <div class="flex justify-center mb-6"> | ||
| 5 | + <span class="w-14 h-14">{{template "logo-icon"}}</span> | ||
| 6 | + </div> | ||
| 7 | + <h1 class="text-2xl font-bold text-spot-text mb-2 text-center">{{.Title}}</h1> | ||
| 8 | + <p class="text-spot-secondary text-sm mb-6 text-center">{{.Message}}</p> | ||
| 9 | + <a href="/dashboard" class="w-full flex items-center justify-center gap-2 bg-spot-green text-white rounded-pill px-4 py-3 text-sm font-bold uppercase tracking-button hover:brightness-110 transition"> | ||
| 10 | + Back to Dashboard | ||
| 11 | + </a> | ||
| 12 | + </div> | ||
| 13 | +</div> | ||
| 14 | +{{end}} | ||