Signed-off-by: brookjeynes me@brookjeynes.dev
+20
internal/atproto/utils.go
+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
+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
+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
+3
internal/views/not-found/not-found.go
History
1 round
0 comments
brookjeynes.dev
submitted
#0
1 commit
expand
collapse
feat(router): add user router
Signed-off-by: brookjeynes <me@brookjeynes.dev>
expand 0 comments
pull request successfully merged