Discover books, shows, and movies at your level. Track your progress by filling your Shelf with what you find, and share with other language learners. *No dusting required. shlf.space

feat(server/login): add login router

Signed-off-by: brookjeynes <me@brookjeynes.dev>

authored by brookjeynes.dev and committed by tangled.org 345a1616 9e6acb09

+88 -5
+7 -1
input.css
··· 1 - @import "tailwindcss"; 1 + @import "tailwindcss"; 2 + 3 + @utility container { 4 + margin-inline: auto; 5 + padding: 2rem 4rem; 6 + max-width: 42rem; 7 + }
+23
internal/server/htmx/htmx.go
··· 1 + package htmx 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + ) 7 + 8 + func HxNotice(w http.ResponseWriter, id, msg string) { 9 + html := fmt.Sprintf(`<span id="%s" hx-swap-oob="innerHTML">%s</span>`, id, msg) 10 + w.Header().Set("Content-Type", "text/html") 11 + w.WriteHeader(http.StatusOK) 12 + w.Write([]byte(html)) 13 + } 14 + 15 + func HxError(w http.ResponseWriter, status int, msg string) { 16 + w.WriteHeader(status) 17 + w.Write([]byte(msg)) 18 + } 19 + 20 + func HxRedirect(w http.ResponseWriter, status int, location string) { 21 + w.Header().Set("HX-Redirect", location) 22 + w.WriteHeader(status) 23 + }
+3 -3
internal/server/index.go
··· 3 3 import ( 4 4 "net/http" 5 5 6 - "shelf.app/internal/server/views" 6 + "shelf.app/internal/views/index" 7 7 ) 8 8 9 - func (s *Server) HandleIndexPage(w http.ResponseWriter, r *http.Request) { 10 - views.IndexPage(views.IndexPageParams{}).Render(r.Context(), w) 9 + func (s *Server) Index(w http.ResponseWriter, r *http.Request) { 10 + index.IndexPage(index.IndexPageParams{}).Render(r.Context(), w) 11 11 }
+50
internal/server/login.go
··· 1 + package server 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + "strings" 7 + 8 + "shelf.app/internal/server/htmx" 9 + "shelf.app/internal/views/login" 10 + ) 11 + 12 + func (s *Server) Login(w http.ResponseWriter, r *http.Request) { 13 + switch r.Method { 14 + case http.MethodGet: 15 + returnURL := r.URL.Query().Get("return_url") 16 + errorCode := r.URL.Query().Get("error") 17 + login.LoginPage(login.LoginPageParams{ 18 + ReturnUrl: returnURL, 19 + ErrorCode: errorCode, 20 + }).Render(r.Context(), w) 21 + case http.MethodPost: 22 + handle := r.FormValue("handle") 23 + returnURL := r.FormValue("return_url") 24 + 25 + // When users copy their handle from bsky.app, it tends to have these 26 + // characters around it: 27 + // 28 + // @nelind.dk: 29 + // \u202a ensures that the handle is always rendered left to right and 30 + // \u202c reverts that so the rest of the page renders however it should 31 + handle = strings.TrimPrefix(handle, "\u202a") 32 + handle = strings.TrimSuffix(handle, "\u202c") 33 + 34 + // `@` is harmless 35 + handle = strings.TrimPrefix(handle, "@") 36 + 37 + // Basic handle validation 38 + if !strings.Contains(handle, ".") { 39 + w.Header().Set("Content-Type", "text/html") 40 + login.LoginFormContent(login.LoginFormParams{ 41 + ReturnUrl: returnURL, 42 + Handle: handle, 43 + ErrorMessage: fmt.Sprintf("'%s' is an invalid handle. Did you mean %s.bsky.social?", handle, handle), 44 + }).Render(r.Context(), w) 45 + return 46 + } 47 + 48 + htmx.HxRedirect(w, http.StatusOK, "/") 49 + } 50 + }
+5 -1
internal/server/router.go
··· 9 9 func (s *Server) Router() http.Handler { 10 10 router := chi.NewRouter() 11 11 12 - router.Get("/", s.HandleIndexPage) 13 12 router.Handle("/static/*", s.HandleStatic()) 13 + 14 + router.Get("/", s.Index) 15 + 16 + router.Get("/login", s.Login) 17 + router.Post("/login", s.Login) 14 18 15 19 return router 16 20 }