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(router): add user router #9

merged opened by brookjeynes.dev targeting master from push-rswysvyovqrl
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:4mj54vc4ha3lh32ksxwunnbh/sh.tangled.repo.pull/3mgjzudi53d22
+152
Diff #0
+20
internal/atproto/utils.go
··· 1 + package atproto 2 + 3 + import ( 4 + "regexp" 5 + ) 6 + 7 + var ( 8 + // ref: https://atproto.com/specs/handle 9 + handleRegex = regexp.MustCompile(`^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`) 10 + // ref: https://atproto.com/specs/did 11 + didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`) 12 + ) 13 + 14 + func IsHandle(s string) bool { 15 + return handleRegex.MatchString(s) 16 + } 17 + 18 + func IsDid(s string) bool { 19 + return didRegex.MatchString(s) 20 + }
+60
internal/server/middleware/middleware.go
··· 1 + package middleware 2 + 3 + import ( 4 + "context" 5 + "log/slog" 6 + "net/http" 7 + "slices" 8 + "strings" 9 + 10 + "github.com/go-chi/chi/v5" 11 + "shlf.space/internal/atproto" 12 + "shlf.space/internal/server/oauth" 13 + notfound "shlf.space/internal/views/not-found" 14 + ) 15 + 16 + type CtxKey string 17 + 18 + const UnreadNotificationCountCtxKey CtxKey = "unreadNotificationCount" 19 + 20 + type Middleware struct { 21 + oauth *oauth.OAuth 22 + idResolver *atproto.Resolver 23 + } 24 + 25 + func New(oauth *oauth.OAuth, idResolver *atproto.Resolver) Middleware { 26 + return Middleware{ 27 + oauth: oauth, 28 + idResolver: idResolver, 29 + } 30 + } 31 + 32 + type middlewareFunc func(http.Handler) http.Handler 33 + 34 + func (mw Middleware) ResolveIdent() middlewareFunc { 35 + excluded := []string{"favicon.ico", "favicon.svg"} 36 + 37 + return func(next http.Handler) http.Handler { 38 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 + didOrHandle := chi.URLParam(r, "user") 40 + didOrHandle = strings.TrimPrefix(didOrHandle, "@") 41 + 42 + if slices.Contains(excluded, didOrHandle) { 43 + next.ServeHTTP(w, r) 44 + return 45 + } 46 + 47 + id, err := mw.idResolver.ResolveIdent(r.Context(), didOrHandle) 48 + if err != nil { 49 + slog.Error("failed to resolve did/handle", "err", err) 50 + w.WriteHeader(http.StatusNotFound) 51 + notfound.NotFoundPage(notfound.NotFoundParams{}).Render(r.Context(), w) 52 + return 53 + } 54 + 55 + ctx := context.WithValue(r.Context(), "resolvedId", *id) 56 + 57 + next.ServeHTTP(w, r.WithContext(ctx)) 58 + }) 59 + } 60 + }
+60
internal/server/router.go
··· 2 2 3 3 import ( 4 4 "net/http" 5 + "strings" 5 6 6 7 "github.com/go-chi/chi/v5" 8 + "shlf.space/internal/atproto" 9 + "shlf.space/internal/server/middleware" 10 + notfound "shlf.space/internal/views/not-found" 7 11 ) 8 12 9 13 func (s *Server) Router() http.Handler { 10 14 router := chi.NewRouter() 15 + middleware := middleware.New( 16 + s.oauth, 17 + s.idResolver, 18 + ) 19 + 20 + userRouter := s.UserRouter(&middleware) 21 + standardRouter := s.StandardRouter(&middleware) 22 + 23 + router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { 24 + pat := chi.URLParam(r, "*") 25 + pathParts := strings.SplitN(pat, "/", 2) 26 + 27 + if len(pathParts) > 0 { 28 + firstPart := pathParts[0] 29 + 30 + // if using a DID or handle, just continue as per usual 31 + if atproto.IsDid(firstPart) || atproto.IsHandle(firstPart) { 32 + userRouter.ServeHTTP(w, r) 33 + return 34 + } 35 + 36 + // if using a handle with @, rewrite to work without @ 37 + if normalized := strings.TrimPrefix(firstPart, "@"); atproto.IsHandle(normalized) { 38 + redirectPath := strings.Join(append([]string{normalized}, pathParts[1:]...), "/") 39 + 40 + redirectURL := *r.URL 41 + redirectURL.Path = "/" + redirectPath 42 + 43 + http.Redirect(w, r, redirectURL.String(), http.StatusFound) 44 + return 45 + } 46 + 47 + } 48 + 49 + standardRouter.ServeHTTP(w, r) 50 + }) 51 + 52 + return router 53 + } 54 + 55 + func (s *Server) StandardRouter(middleware *middleware.Middleware) http.Handler { 56 + router := chi.NewRouter() 11 57 12 58 router.Handle("/static/*", s.HandleStatic()) 13 59 ··· 21 67 22 68 return router 23 69 } 70 + 71 + func (s *Server) UserRouter(middleware *middleware.Middleware) http.Handler { 72 + router := chi.NewRouter() 73 + 74 + router.With(middleware.ResolveIdent()).Route("/{user}", func(r chi.Router) { 75 + }) 76 + 77 + router.NotFound(func(w http.ResponseWriter, r *http.Request) { 78 + w.WriteHeader(http.StatusNotFound) 79 + notfound.NotFoundPage(notfound.NotFoundParams{}).Render(r.Context(), w) 80 + }) 81 + 82 + return router 83 + }
+3
internal/views/not-found/not-found.go
··· 1 + package notfound 2 + 3 + type NotFoundParams struct{}
+9
internal/views/not-found/not-found.templ
··· 1 + package notfound 2 + 3 + import "shlf.space/internal/layouts/base" 4 + 5 + templ NotFoundPage(params NotFoundParams) { 6 + @layouts.Base(layouts.BaseParams{Title: "not found"}) { 7 + <div class="container">not found...</div> 8 + } 9 + }

History

1 round 0 comments
sign up or login to add to the discussion
brookjeynes.dev submitted #0
1 commit
expand
feat(router): add user router
expand 0 comments
pull request successfully merged