diff --git a/appview/middleware/middleware.go b/appview/middleware/middleware.go --- a/appview/middleware/middleware.go +++ b/appview/middleware/middleware.go @@ -180,12 +180,12 @@ return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { didOrHandle := chi.URLParam(req, "user") + didOrHandle = strings.TrimPrefix(didOrHandle, "@") + if slices.Contains(excluded, didOrHandle) { next.ServeHTTP(w, req) return } - - didOrHandle = strings.TrimPrefix(didOrHandle, "@") id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle) if err != nil { diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -17,6 +17,7 @@ "strings" "time" + "github.com/bluesky-social/indigo/atproto/syntax" "github.com/dustin/go-humanize" "github.com/go-enry/go-enry/v2" "tangled.org/core/appview/filetree" @@ -63,7 +64,7 @@ return "handle.invalid" } - return "@" + identity.Handle.String() + return identity.Handle.String() }, "truncateAt30": func(s string) string { if len(s) <= 30 { @@ -123,8 +124,8 @@ return b }, "didOrHandle": func(did, handle string) string { - if handle != "" { - return fmt.Sprintf("@%s", handle) + if handle != "" && handle != syntax.HandleInvalid.String() { + return handle } else { return did } diff --git a/appview/state/router.go b/appview/state/router.go --- a/appview/state/router.go +++ b/appview/state/router.go @@ -42,32 +42,34 @@ router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { pat := chi.URLParam(r, "*") - if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") { - userRouter.ServeHTTP(w, r) - } else { - // Check if the first path element is a valid handle without '@' or a flattened DID - pathParts := strings.SplitN(pat, "/", 2) - if len(pathParts) > 0 { - if userutil.IsHandleNoAt(pathParts[0]) { - // Redirect to the same path but with '@' prefixed to the handle - redirectPath := "@" + pat - http.Redirect(w, r, "/"+redirectPath, http.StatusFound) - return - } else if userutil.IsFlattenedDid(pathParts[0]) { - // Redirect to the unflattened DID version - unflattenedDid := userutil.UnflattenDid(pathParts[0]) - var redirectPath string - if len(pathParts) > 1 { - redirectPath = unflattenedDid + "/" + pathParts[1] - } else { - redirectPath = unflattenedDid - } - http.Redirect(w, r, "/"+redirectPath, http.StatusFound) - return - } + pathParts := strings.SplitN(pat, "/", 2) + + if len(pathParts) > 0 { + firstPart := pathParts[0] + + // if using a DID or handle, just continue as per usual + if userutil.IsDid(firstPart) || userutil.IsHandle(firstPart) { + userRouter.ServeHTTP(w, r) + return } - standardRouter.ServeHTTP(w, r) + + // if using a flattened DID (like you would in go modules), unflatten + if userutil.IsFlattenedDid(firstPart) { + unflattenedDid := userutil.UnflattenDid(firstPart) + redirectPath := strings.Join(append([]string{unflattenedDid}, pathParts[1:]...), "/") + http.Redirect(w, r, "/"+redirectPath, http.StatusFound) + return + } + + // if using a handle with @, rewrite to work without @ + if normalized := strings.TrimPrefix(firstPart, "@"); userutil.IsHandle(normalized) { + redirectPath := strings.Join(append([]string{normalized}, pathParts[1:]...), "/") + http.Redirect(w, r, "/"+redirectPath, http.StatusFound) + return + } } + + standardRouter.ServeHTTP(w, r) }) return router diff --git a/appview/state/state.go b/appview/state/state.go --- a/appview/state/state.go +++ b/appview/state/state.go @@ -386,12 +386,13 @@ pubKeys, err := db.GetPublicKeysForDid(s.db, id.DID.String()) if err != nil { - w.WriteHeader(http.StatusNotFound) + s.logger.Error("failed to get public keys", "err", err) + http.Error(w, "failed to get public keys", http.StatusInternalServerError) return } if len(pubKeys) == 0 { - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusNoContent) return } diff --git a/appview/pages/repoinfo/repoinfo.go b/appview/pages/repoinfo/repoinfo.go --- a/appview/pages/repoinfo/repoinfo.go +++ b/appview/pages/repoinfo/repoinfo.go @@ -1,31 +1,29 @@ package repoinfo import ( - "fmt" "path" "slices" - "strings" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" "tangled.org/core/appview/state/userutil" ) -func (r RepoInfo) OwnerWithAt() string { +func (r RepoInfo) Owner() string { if r.OwnerHandle != "" { - return fmt.Sprintf("@%s", r.OwnerHandle) + return r.OwnerHandle } else { return r.OwnerDid } } func (r RepoInfo) FullName() string { - return path.Join(r.OwnerWithAt(), r.Name) + return path.Join(r.Owner(), r.Name) } func (r RepoInfo) OwnerWithoutAt() string { - if after, ok := strings.CutPrefix(r.OwnerWithAt(), "@"); ok { - return after + if r.OwnerHandle != "" { + return r.OwnerHandle } else { return userutil.FlattenDid(r.OwnerDid) } diff --git a/appview/state/userutil/userutil.go b/appview/state/userutil/userutil.go --- a/appview/state/userutil/userutil.go +++ b/appview/state/userutil/userutil.go @@ -10,9 +10,14 @@ didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`) ) -func IsHandleNoAt(s string) bool { +func IsHandle(s string) bool { // ref: https://atproto.com/specs/handle return handleRegex.MatchString(s) +} + +// IsDid checks if the given string is a standard DID. +func IsDid(s string) bool { + return didRegex.MatchString(s) } func UnflattenDid(s string) string { @@ -45,11 +50,6 @@ return strings.Replace(s, ":", "-", 2) } return s -} - -// IsDid checks if the given string is a standard DID. -func IsDid(s string) bool { - return didRegex.MatchString(s) } var subdomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{2,61}[a-z0-9])?$`) diff --git a/appview/pages/templates/layouts/repobase.html b/appview/pages/templates/layouts/repobase.html --- a/appview/pages/templates/layouts/repobase.html +++ b/appview/pages/templates/layouts/repobase.html @@ -13,8 +13,8 @@
{{ end }}https://tangled.org/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }}
+ data-url="https://tangled.org/{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}"
+ >https://tangled.org/{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}