From bdc676d6ace167b1096c42508453a85abd9dbbd0 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Sun, 23 Mar 2025 11:41:38 +0200 Subject: [PATCH] appview: state/userutil: init new pkg for user handle/did utils --- appview/pages/pages.go | 15 ++++- appview/pages/templates/layouts/base.html | 2 + appview/pages/templates/layouts/repobase.html | 9 +++ appview/state/router.go | 50 ++------------- appview/state/userutil/userutil.go | 62 +++++++++++++++++++ .../userutil_test.go} | 6 +- 6 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 appview/state/userutil/userutil.go rename appview/state/{router_test.go => userutil/userutil_test.go} (97%) diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 32a5015e..f38df42f 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -22,6 +22,7 @@ import ( "github.com/microcosm-cc/bluemonday" "github.com/sotangled/tangled/appview/auth" "github.com/sotangled/tangled/appview/db" + "github.com/sotangled/tangled/appview/state/userutil" "github.com/sotangled/tangled/types" ) @@ -252,6 +253,18 @@ func (r RepoInfo) FullName() string { return path.Join(r.OwnerWithAt(), r.Name) } +func (r RepoInfo) OwnerWithoutAt() string { + if strings.HasPrefix(r.OwnerWithAt(), "@") { + return strings.TrimPrefix(r.OwnerWithAt(), "@") + } else { + return userutil.FlattenDid(r.OwnerDid) + } +} + +func (r RepoInfo) FullNameWithoutAt() string { + return path.Join(r.OwnerWithoutAt(), r.Name) +} + func (r RepoInfo) GetTabs() [][]string { tabs := [][]string{ {"overview", "/"}, @@ -328,7 +341,7 @@ type RepoLogParams struct { LoggedInUser *auth.User RepoInfo RepoInfo types.RepoLogResponse - Active string + Active string EmailToDidOrHandle map[string]string } diff --git a/appview/pages/templates/layouts/base.html b/appview/pages/templates/layouts/base.html index b9c3bdf6..1c7aa675 100644 --- a/appview/pages/templates/layouts/base.html +++ b/appview/pages/templates/layouts/base.html @@ -9,7 +9,9 @@ /> + {{ block "title" . }}{{ end }} ยท tangled + {{ block "extrameta" . }}{{ end }}
diff --git a/appview/pages/templates/layouts/repobase.html b/appview/pages/templates/layouts/repobase.html index 64369f51..71f873b3 100644 --- a/appview/pages/templates/layouts/repobase.html +++ b/appview/pages/templates/layouts/repobase.html @@ -1,5 +1,14 @@ {{ define "title" }}{{ .RepoInfo.FullName }}{{ end }} +{{ define "extrameta" }} + + + + + + +{{ end }} + {{ define "content" }}

diff --git a/appview/state/router.go b/appview/state/router.go index fd4256fc..5d1b2b26 100644 --- a/appview/state/router.go +++ b/appview/state/router.go @@ -2,10 +2,10 @@ package state import ( "net/http" - "regexp" "strings" "github.com/go-chi/chi/v5" + "github.com/sotangled/tangled/appview/state/userutil" ) func (s *State) Router() http.Handler { @@ -19,14 +19,14 @@ func (s *State) Router() http.Handler { // 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 isHandleNoAt(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 isFlattenedDid(pathParts[0]) { + } else if userutil.IsFlattenedDid(pathParts[0]) { // Redirect to the unflattened DID version - unflattenedDid := unflattenDid(pathParts[0]) + unflattenedDid := userutil.UnflattenDid(pathParts[0]) var redirectPath string if len(pathParts) > 1 { redirectPath = unflattenedDid + "/" + pathParts[1] @@ -44,48 +44,6 @@ func (s *State) Router() http.Handler { return router } -func isHandleNoAt(s string) bool { - // ref: https://atproto.com/specs/handle - re := 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])?$`) - return re.MatchString(s) -} - -func unflattenDid(s string) string { - if !isFlattenedDid(s) { - return s - } - - parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-" - if len(parts) != 2 { - return s - } - - return "did:" + parts[0] + ":" + parts[1] -} - -// isFlattenedDid checks if the given string is a flattened DID. -// A flattened DID is a DID with the :s swapped to -s to satisfy certain -// application requirements, such as Go module naming conventions. -func isFlattenedDid(s string) bool { - // Check if the string starts with "did-" - if !strings.HasPrefix(s, "did-") { - return false - } - - // Split the string to extract method and identifier - parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-" - if len(parts) != 2 { - return false - } - - // Reconstruct as a standard DID format - // Example: "did-plc-xyz-abc" becomes "did:plc:xyz-abc" - reconstructed := "did:" + parts[0] + ":" + parts[1] - re := regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`) - - return re.MatchString(reconstructed) -} - func (s *State) UserRouter() http.Handler { r := chi.NewRouter() diff --git a/appview/state/userutil/userutil.go b/appview/state/userutil/userutil.go new file mode 100644 index 00000000..3a30f42f --- /dev/null +++ b/appview/state/userutil/userutil.go @@ -0,0 +1,62 @@ +package userutil + +import ( + "regexp" + "strings" +) + +func IsHandleNoAt(s string) bool { + // ref: https://atproto.com/specs/handle + re := 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])?$`) + return re.MatchString(s) +} + +func UnflattenDid(s string) string { + if !IsFlattenedDid(s) { + return s + } + + parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-" + if len(parts) != 2 { + return s + } + + return "did:" + parts[0] + ":" + parts[1] +} + +// IsFlattenedDid checks if the given string is a flattened DID. +func IsFlattenedDid(s string) bool { + // Check if the string starts with "did-" + if !strings.HasPrefix(s, "did-") { + return false + } + + // Split the string to extract method and identifier + parts := strings.SplitN(s[4:], "-", 2) // Skip "did-" prefix and split on first "-" + if len(parts) != 2 { + return false + } + + // Reconstruct as a standard DID format + // Example: "did-plc-xyz-abc" becomes "did:plc:xyz-abc" + reconstructed := "did:" + parts[0] + ":" + parts[1] + re := regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`) + + return re.MatchString(reconstructed) +} + +// FlattenDid converts a DID to a flattened format. +// A flattened DID is a DID with the :s swapped to -s to satisfy certain +// application requirements, such as Go module naming conventions. +func FlattenDid(s string) string { + if !IsFlattenedDid(s) { + return s + } + + parts := strings.SplitN(s[4:], ":", 2) // Skip "did:" prefix and split on first ":" + if len(parts) != 2 { + return s + } + + return "did-" + parts[0] + "-" + parts[1] +} diff --git a/appview/state/router_test.go b/appview/state/userutil/userutil_test.go similarity index 97% rename from appview/state/router_test.go rename to appview/state/userutil/userutil_test.go index e0c09ea1..1c6ad23d 100644 --- a/appview/state/router_test.go +++ b/appview/state/userutil/userutil_test.go @@ -1,4 +1,4 @@ -package state +package userutil import "testing" @@ -36,7 +36,7 @@ func TestUnflattenDid(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - result := unflattenDid(tc.input) + result := UnflattenDid(tc.input) if result != tc.expected { t.Errorf("unflattenDid(%q) = %q, want %q", tc.input, result, tc.expected) } @@ -105,7 +105,7 @@ var isFlattenedDidTests = []struct { func TestIsFlattenedDid(t *testing.T) { for _, tc := range isFlattenedDidTests { t.Run(tc.name, func(t *testing.T) { - result := isFlattenedDid(tc.input) + result := IsFlattenedDid(tc.input) if result != tc.expected { t.Errorf("isFlattenedDid(%q) = %v, want %v", tc.input, result, tc.expected) } -- 2.51.2