A social RSS reader built on the AT Protocol. glean.at
glean atproto atmosphere rss feed social app
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Pretty error handling

+38 -11
+9 -9
internal/server/auth_handler.go
··· 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 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 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 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 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 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
+2 -2
internal/server/profile_handler.go
··· 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 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
+13
internal/server/server.go
··· 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{}
+14
internal/tmpl/error.html
··· 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}}