+2
-2
appview/middleware/middleware.go
+2
-2
appview/middleware/middleware.go
···
180
180
return func(next http.Handler) http.Handler {
181
181
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
182
182
didOrHandle := chi.URLParam(req, "user")
183
+
didOrHandle = strings.TrimPrefix(didOrHandle, "@")
184
+
183
185
if slices.Contains(excluded, didOrHandle) {
184
186
next.ServeHTTP(w, req)
185
187
return
186
188
}
187
-
188
-
didOrHandle = strings.TrimPrefix(didOrHandle, "@")
189
189
190
190
id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle)
191
191
if err != nil {
+4
-3
appview/pages/funcmap.go
+4
-3
appview/pages/funcmap.go
···
17
17
"strings"
18
18
"time"
19
19
20
+
"github.com/bluesky-social/indigo/atproto/syntax"
20
21
"github.com/dustin/go-humanize"
21
22
"github.com/go-enry/go-enry/v2"
22
23
"tangled.org/core/appview/filetree"
···
63
64
return "handle.invalid"
64
65
}
65
66
66
-
return "@" + identity.Handle.String()
67
+
return identity.Handle.String()
67
68
},
68
69
"truncateAt30": func(s string) string {
69
70
if len(s) <= 30 {
···
123
124
return b
124
125
},
125
126
"didOrHandle": func(did, handle string) string {
126
-
if handle != "" {
127
-
return fmt.Sprintf("@%s", handle)
127
+
if handle != "" && handle != syntax.HandleInvalid.String() {
128
+
return handle
128
129
} else {
129
130
return did
130
131
}
+5
-7
appview/pages/repoinfo/repoinfo.go
+5
-7
appview/pages/repoinfo/repoinfo.go
···
1
1
package repoinfo
2
2
3
3
import (
4
-
"fmt"
5
4
"path"
6
5
"slices"
7
-
"strings"
8
6
9
7
"github.com/bluesky-social/indigo/atproto/syntax"
10
8
"tangled.org/core/appview/models"
11
9
"tangled.org/core/appview/state/userutil"
12
10
)
13
11
14
-
func (r RepoInfo) OwnerWithAt() string {
12
+
func (r RepoInfo) Owner() string {
15
13
if r.OwnerHandle != "" {
16
-
return fmt.Sprintf("@%s", r.OwnerHandle)
14
+
return r.OwnerHandle
17
15
} else {
18
16
return r.OwnerDid
19
17
}
20
18
}
21
19
22
20
func (r RepoInfo) FullName() string {
23
-
return path.Join(r.OwnerWithAt(), r.Name)
21
+
return path.Join(r.Owner(), r.Name)
24
22
}
25
23
26
24
func (r RepoInfo) OwnerWithoutAt() string {
27
-
if after, ok := strings.CutPrefix(r.OwnerWithAt(), "@"); ok {
28
-
return after
25
+
if r.OwnerHandle != "" {
26
+
return r.OwnerHandle
29
27
} else {
30
28
return userutil.FlattenDid(r.OwnerDid)
31
29
}
+2
-2
appview/pages/templates/layouts/repobase.html
+2
-2
appview/pages/templates/layouts/repobase.html
···
13
13
</p>
14
14
{{ end }}
15
15
<div class="text-lg flex items-center justify-between">
16
-
<div>
17
-
<a href="/{{ .RepoInfo.OwnerWithAt }}">{{ .RepoInfo.OwnerWithAt }}</a>
16
+
<div class="flex items-center gap-2 flex-wrap">
17
+
{{ template "user/fragments/picHandleLink" .RepoInfo.OwnerDid }}
18
18
<span class="select-none">/</span>
19
19
<a href="/{{ .RepoInfo.FullName }}" class="font-bold">{{ .RepoInfo.Name }}</a>
20
20
</div>
+2
-2
appview/pages/templates/repo/fragments/cloneDropdown.html
+2
-2
appview/pages/templates/repo/fragments/cloneDropdown.html
···
29
29
<code
30
30
class="flex-1 px-3 py-2 text-sm bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-l select-all cursor-pointer whitespace-nowrap overflow-x-auto"
31
31
onclick="window.getSelection().selectAllChildren(this)"
32
-
data-url="https://tangled.org/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }}"
33
-
>https://tangled.org/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }}</code>
32
+
data-url="https://tangled.org/{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}"
33
+
>https://tangled.org/{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}</code>
34
34
<button
35
35
onclick="copyToClipboard(this, this.previousElementSibling.getAttribute('data-url'))"
36
36
class="px-3 py-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 border-l border-gray-300 dark:border-gray-600"
+26
-24
appview/state/router.go
+26
-24
appview/state/router.go
···
42
42
43
43
router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
44
44
pat := chi.URLParam(r, "*")
45
-
if strings.HasPrefix(pat, "did:") || strings.HasPrefix(pat, "@") {
46
-
userRouter.ServeHTTP(w, r)
47
-
} else {
48
-
// Check if the first path element is a valid handle without '@' or a flattened DID
49
-
pathParts := strings.SplitN(pat, "/", 2)
50
-
if len(pathParts) > 0 {
51
-
if userutil.IsHandleNoAt(pathParts[0]) {
52
-
// Redirect to the same path but with '@' prefixed to the handle
53
-
redirectPath := "@" + pat
54
-
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
55
-
return
56
-
} else if userutil.IsFlattenedDid(pathParts[0]) {
57
-
// Redirect to the unflattened DID version
58
-
unflattenedDid := userutil.UnflattenDid(pathParts[0])
59
-
var redirectPath string
60
-
if len(pathParts) > 1 {
61
-
redirectPath = unflattenedDid + "/" + pathParts[1]
62
-
} else {
63
-
redirectPath = unflattenedDid
64
-
}
65
-
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
66
-
return
67
-
}
45
+
pathParts := strings.SplitN(pat, "/", 2)
46
+
47
+
if len(pathParts) > 0 {
48
+
firstPart := pathParts[0]
49
+
50
+
// if using a DID or handle, just continue as per usual
51
+
if userutil.IsDid(firstPart) || userutil.IsHandle(firstPart) {
52
+
userRouter.ServeHTTP(w, r)
53
+
return
68
54
}
69
-
standardRouter.ServeHTTP(w, r)
55
+
56
+
// if using a flattened DID (like you would in go modules), unflatten
57
+
if userutil.IsFlattenedDid(firstPart) {
58
+
unflattenedDid := userutil.UnflattenDid(firstPart)
59
+
redirectPath := strings.Join(append([]string{unflattenedDid}, pathParts[1:]...), "/")
60
+
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
61
+
return
62
+
}
63
+
64
+
// if using a handle with @, rewrite to work without @
65
+
if normalized := strings.TrimPrefix(firstPart, "@"); userutil.IsHandle(normalized) {
66
+
redirectPath := strings.Join(append([]string{normalized}, pathParts[1:]...), "/")
67
+
http.Redirect(w, r, "/"+redirectPath, http.StatusFound)
68
+
return
69
+
}
70
70
}
71
+
72
+
standardRouter.ServeHTTP(w, r)
71
73
})
72
74
73
75
return router
+3
-2
appview/state/state.go
+3
-2
appview/state/state.go
···
386
386
387
387
pubKeys, err := db.GetPublicKeysForDid(s.db, id.DID.String())
388
388
if err != nil {
389
-
w.WriteHeader(http.StatusNotFound)
389
+
s.logger.Error("failed to get public keys", "err", err)
390
+
http.Error(w, "failed to get public keys", http.StatusInternalServerError)
390
391
return
391
392
}
392
393
393
394
if len(pubKeys) == 0 {
394
-
w.WriteHeader(http.StatusNotFound)
395
+
w.WriteHeader(http.StatusNoContent)
395
396
return
396
397
}
397
398
+6
-6
appview/state/userutil/userutil.go
+6
-6
appview/state/userutil/userutil.go
···
10
10
didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
11
11
)
12
12
13
-
func IsHandleNoAt(s string) bool {
13
+
func IsHandle(s string) bool {
14
14
// ref: https://atproto.com/specs/handle
15
15
return handleRegex.MatchString(s)
16
+
}
17
+
18
+
// IsDid checks if the given string is a standard DID.
19
+
func IsDid(s string) bool {
20
+
return didRegex.MatchString(s)
16
21
}
17
22
18
23
func UnflattenDid(s string) string {
···
45
50
return strings.Replace(s, ":", "-", 2)
46
51
}
47
52
return s
48
-
}
49
-
50
-
// IsDid checks if the given string is a standard DID.
51
-
func IsDid(s string) bool {
52
-
return didRegex.MatchString(s)
53
53
}
54
54
55
55
var subdomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{2,61}[a-z0-9])?$`)