+2
-2
appview/middleware/middleware.go
+2
-2
appview/middleware/middleware.go
···
180
return func(next http.Handler) http.Handler {
181
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
182
didOrHandle := chi.URLParam(req, "user")
183
if slices.Contains(excluded, didOrHandle) {
184
next.ServeHTTP(w, req)
185
return
186
}
187
-
188
-
didOrHandle = strings.TrimPrefix(didOrHandle, "@")
189
190
id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle)
191
if err != nil {
···
180
return func(next http.Handler) http.Handler {
181
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
182
didOrHandle := chi.URLParam(req, "user")
183
+
didOrHandle = strings.TrimPrefix(didOrHandle, "@")
184
+
185
if slices.Contains(excluded, didOrHandle) {
186
next.ServeHTTP(w, req)
187
return
188
}
189
190
id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle)
191
if err != nil {
+4
-3
appview/pages/funcmap.go
+4
-3
appview/pages/funcmap.go
···
17
"strings"
18
"time"
19
20
"github.com/dustin/go-humanize"
21
"github.com/go-enry/go-enry/v2"
22
"tangled.org/core/appview/filetree"
···
63
return "handle.invalid"
64
}
65
66
-
return "@" + identity.Handle.String()
67
},
68
"truncateAt30": func(s string) string {
69
if len(s) <= 30 {
···
123
return b
124
},
125
"didOrHandle": func(did, handle string) string {
126
-
if handle != "" {
127
-
return fmt.Sprintf("@%s", handle)
128
} else {
129
return did
130
}
···
17
"strings"
18
"time"
19
20
+
"github.com/bluesky-social/indigo/atproto/syntax"
21
"github.com/dustin/go-humanize"
22
"github.com/go-enry/go-enry/v2"
23
"tangled.org/core/appview/filetree"
···
64
return "handle.invalid"
65
}
66
67
+
return identity.Handle.String()
68
},
69
"truncateAt30": func(s string) string {
70
if len(s) <= 30 {
···
124
return b
125
},
126
"didOrHandle": func(did, handle string) string {
127
+
if handle != "" && handle != syntax.HandleInvalid.String() {
128
+
return handle
129
} else {
130
return did
131
}
+5
-7
appview/pages/repoinfo/repoinfo.go
+5
-7
appview/pages/repoinfo/repoinfo.go
···
1
package repoinfo
2
3
import (
4
-
"fmt"
5
"path"
6
"slices"
7
-
"strings"
8
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
"tangled.org/core/appview/models"
11
"tangled.org/core/appview/state/userutil"
12
)
13
14
-
func (r RepoInfo) OwnerWithAt() string {
15
if r.OwnerHandle != "" {
16
-
return fmt.Sprintf("@%s", r.OwnerHandle)
17
} else {
18
return r.OwnerDid
19
}
20
}
21
22
func (r RepoInfo) FullName() string {
23
-
return path.Join(r.OwnerWithAt(), r.Name)
24
}
25
26
func (r RepoInfo) OwnerWithoutAt() string {
27
-
if after, ok := strings.CutPrefix(r.OwnerWithAt(), "@"); ok {
28
-
return after
29
} else {
30
return userutil.FlattenDid(r.OwnerDid)
31
}
···
1
package repoinfo
2
3
import (
4
"path"
5
"slices"
6
7
"github.com/bluesky-social/indigo/atproto/syntax"
8
"tangled.org/core/appview/models"
9
"tangled.org/core/appview/state/userutil"
10
)
11
12
+
func (r RepoInfo) Owner() string {
13
if r.OwnerHandle != "" {
14
+
return r.OwnerHandle
15
} else {
16
return r.OwnerDid
17
}
18
}
19
20
func (r RepoInfo) FullName() string {
21
+
return path.Join(r.Owner(), r.Name)
22
}
23
24
func (r RepoInfo) OwnerWithoutAt() string {
25
+
if r.OwnerHandle != "" {
26
+
return r.OwnerHandle
27
} else {
28
return userutil.FlattenDid(r.OwnerDid)
29
}
+2
-2
appview/pages/templates/layouts/repobase.html
+2
-2
appview/pages/templates/layouts/repobase.html
···
13
</p>
14
{{ end }}
15
<div class="text-lg flex items-center justify-between">
16
-
<div>
17
-
<a href="/{{ .RepoInfo.OwnerWithAt }}">{{ .RepoInfo.OwnerWithAt }}</a>
18
<span class="select-none">/</span>
19
<a href="/{{ .RepoInfo.FullName }}" class="font-bold">{{ .RepoInfo.Name }}</a>
20
</div>
···
13
</p>
14
{{ end }}
15
<div class="text-lg flex items-center justify-between">
16
+
<div class="flex items-center gap-2 flex-wrap">
17
+
{{ template "user/fragments/picHandleLink" .RepoInfo.OwnerDid }}
18
<span class="select-none">/</span>
19
<a href="/{{ .RepoInfo.FullName }}" class="font-bold">{{ .RepoInfo.Name }}</a>
20
</div>
+2
-2
appview/pages/templates/repo/fragments/cloneDropdown.html
+2
-2
appview/pages/templates/repo/fragments/cloneDropdown.html
···
29
<code
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
onclick="window.getSelection().selectAllChildren(this)"
32
-
data-url="https://tangled.org/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }}"
33
-
>https://tangled.org/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }}</code>
34
<button
35
onclick="copyToClipboard(this, this.previousElementSibling.getAttribute('data-url'))"
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"
···
29
<code
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
onclick="window.getSelection().selectAllChildren(this)"
32
+
data-url="https://tangled.org/{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}"
33
+
>https://tangled.org/{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}</code>
34
<button
35
onclick="copyToClipboard(this, this.previousElementSibling.getAttribute('data-url'))"
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
43
router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
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
-
}
68
}
69
-
standardRouter.ServeHTTP(w, r)
70
}
71
})
72
73
return router
···
42
43
router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
44
pat := chi.URLParam(r, "*")
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
54
}
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
}
71
+
72
+
standardRouter.ServeHTTP(w, r)
73
})
74
75
return router
+3
-2
appview/state/state.go
+3
-2
appview/state/state.go
···
386
387
pubKeys, err := db.GetPublicKeysForDid(s.db, id.DID.String())
388
if err != nil {
389
+
s.logger.Error("failed to get public keys", "err", err)
390
+
http.Error(w, "failed to get public keys", http.StatusInternalServerError)
391
return
392
}
393
394
if len(pubKeys) == 0 {
395
+
w.WriteHeader(http.StatusNoContent)
396
return
397
}
398
+6
-6
appview/state/userutil/userutil.go
+6
-6
appview/state/userutil/userutil.go
···
10
didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
11
)
12
13
-
func IsHandleNoAt(s string) bool {
14
// ref: https://atproto.com/specs/handle
15
return handleRegex.MatchString(s)
16
}
17
18
func UnflattenDid(s string) string {
···
45
return strings.Replace(s, ":", "-", 2)
46
}
47
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
}
54
55
var subdomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{2,61}[a-z0-9])?$`)
···
10
didRegex = regexp.MustCompile(`^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$`)
11
)
12
13
+
func IsHandle(s string) bool {
14
// ref: https://atproto.com/specs/handle
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)
21
}
22
23
func UnflattenDid(s string) string {
···
50
return strings.Replace(s, ":", "-", 2)
51
}
52
return s
53
}
54
55
var subdomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{2,61}[a-z0-9])?$`)