diff --git a/appview/pages/pages.go b/appview/pages/pages.go
index 04e767f6..0143fd57 100644
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -85,3 +85,14 @@ type NewRepoParams struct {
func NewRepo(w io.Writer, p NewRepoParams) error {
return parse("new-repo.html").Execute(w, p)
}
+
+type ProfilePageParams struct {
+ LoggedInUser *auth.User
+ UserDid string
+ UserHandle string
+ Repos []db.Repo
+}
+
+func ProfilePage(w io.Writer, p ProfilePageParams) error {
+ return parse("profile.html").Execute(w, p)
+}
diff --git a/appview/pages/profile.html b/appview/pages/profile.html
new file mode 100644
index 00000000..0a3971aa
--- /dev/null
+++ b/appview/pages/profile.html
@@ -0,0 +1,18 @@
+{{define "title"}}{{ or .UserHandle .UserDid }}{{end}}
+
+{{define "content"}}
+ back to timeline
+
{{ or .UserHandle .UserDid }} profile
+
+ repos
+
+{{end}}
diff --git a/appview/state/middleware.go b/appview/state/middleware.go
index daa3914f..b0e71315 100644
--- a/appview/state/middleware.go
+++ b/appview/state/middleware.go
@@ -3,6 +3,7 @@ package state
import (
"log"
"net/http"
+ "strings"
"time"
comatproto "github.com/bluesky-social/indigo/api/atproto"
@@ -99,3 +100,13 @@ func RoleMiddleware(s *State, group string) Middleware {
})
}
}
+
+func StripLeadingAt(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ path := req.URL.Path
+ if strings.HasPrefix(path, "/@") {
+ req.URL.Path = "/" + strings.TrimPrefix(path, "/@")
+ }
+ next.ServeHTTP(w, req)
+ })
+}
diff --git a/appview/state/state.go b/appview/state/state.go
index f1d7102f..d7e8e1a9 100644
--- a/appview/state/state.go
+++ b/appview/state/state.go
@@ -538,7 +538,62 @@ func (s *State) AddRepo(w http.ResponseWriter, r *http.Request) {
}
}
+func (s *State) ProfilePage(w http.ResponseWriter, r *http.Request) {
+ didOrHandle := chi.URLParam(r, "user")
+ if didOrHandle == "" {
+ http.Error(w, "Bad request", http.StatusBadRequest)
+ return
+ }
+
+ ident, err := auth.ResolveIdent(r.Context(), didOrHandle)
+ if err != nil {
+ log.Printf("resolving identity: %s", err)
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ repos, err := s.db.GetAllReposByDid(ident.DID.String())
+ if err != nil {
+ log.Printf("getting repos for %s: %s", ident.DID.String(), err)
+ }
+
+ pages.ProfilePage(w, pages.ProfilePageParams{
+ LoggedInUser: s.auth.GetUser(r),
+ UserDid: ident.DID.String(),
+ UserHandle: ident.Handle.String(),
+ Repos: repos,
+ })
+}
+
func (s *State) Router() http.Handler {
+ router := chi.NewRouter()
+
+ router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
+ pat := chi.URLParam(r, "*")
+ if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") {
+ s.UserRouter().ServeHTTP(w, r)
+ } else {
+ s.StandardRouter().ServeHTTP(w, r)
+ }
+ })
+
+ return router
+}
+
+func (s *State) UserRouter() http.Handler {
+ r := chi.NewRouter()
+
+ // strip @ from user
+ r.Use(StripLeadingAt)
+
+ r.Route("/{user}", func(r chi.Router) {
+ r.Get("/", s.ProfilePage)
+ })
+
+ return r
+}
+
+func (s *State) StandardRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", s.Timeline)