appview/pages: resolve did on render #801

merged
opened by boltless.me targeting master from sl/yurolxtlpsmz

Don't pass resolved user handles from http handlers. The page renderer is capable of resolving DIDs and we are using redis cache, so the performance won't matter much either.

Signed-off-by: Seongmin Lee git@boltless.me

+28 -8
appview/pages/funcmap.go
···
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
-
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/dustin/go-humanize"
"github.com/go-enry/go-enry/v2"
"tangled.org/core/appview/filetree"
"tangled.org/core/appview/pages/markup"
"tangled.org/core/crypto"
)
···
return identity.Handle.String()
},
"truncateAt30": func(s string) string {
if len(s) <= 30 {
return s
···
return b
},
-
"didOrHandle": func(did, handle string) string {
-
if handle != "" && handle != syntax.HandleInvalid.String() {
-
return handle
-
} else {
-
return did
-
}
-
},
"assoc": func(values ...string) ([][]string, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments")
···
}
}
func (p *Pages) AvatarUrl(handle, size string) string {
handle = strings.TrimPrefix(handle, "@")
secret := p.avatar.SharedSecret
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(handle))
···
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
"github.com/dustin/go-humanize"
"github.com/go-enry/go-enry/v2"
"tangled.org/core/appview/filetree"
+
"tangled.org/core/appview/models"
"tangled.org/core/appview/pages/markup"
"tangled.org/core/crypto"
)
···
return identity.Handle.String()
},
+
"ownerSlashRepo": func(repo *models.Repo) string {
+
ownerId, err := p.resolver.ResolveIdent(context.Background(), repo.Did)
+
if err != nil {
+
return repo.DidSlashRepo()
+
}
+
handle := ownerId.Handle
+
if handle != "" && !handle.IsInvalidHandle() {
+
return string(handle)+"/"+repo.Name
+
}
+
return repo.DidSlashRepo()
+
},
"truncateAt30": func(s string) string {
if len(s) <= 30 {
return s
···
return b
},
"assoc": func(values ...string) ([][]string, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments")
···
}
}
+
func (p *Pages) resolveDid(did string) string {
+
identity, err := p.resolver.ResolveIdent(context.Background(), did)
+
+
if err != nil {
+
return did
+
}
+
+
if identity.Handle.IsInvalidHandle() {
+
return "handle.invalid"
+
}
+
+
return identity.Handle.String()
+
}
+
func (p *Pages) AvatarUrl(handle, size string) string {
handle = strings.TrimPrefix(handle, "@")
+
handle = p.resolveDid(handle)
+
secret := p.avatar.SharedSecret
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(handle))
-2
appview/pages/pages.go
···
type ProfileCard struct {
UserDid string
-
UserHandle string
FollowStatus models.FollowStatus
Punchcard *models.Punchcard
Profile *models.Profile
···
type Collaborator struct {
Did string
-
Handle string
Role string
}
···
type ProfileCard struct {
UserDid string
FollowStatus models.FollowStatus
Punchcard *models.Punchcard
Profile *models.Profile
···
type Collaborator struct {
Did string
Role string
}
+2 -2
appview/pages/repoinfo/repoinfo.go
···
"tangled.org/core/appview/state/userutil"
)
-
func (r RepoInfo) Owner() string {
if r.OwnerHandle != "" {
return r.OwnerHandle
} else {
···
}
func (r RepoInfo) FullName() string {
-
return path.Join(r.Owner(), r.Name)
}
func (r RepoInfo) OwnerWithoutAt() string {
···
"tangled.org/core/appview/state/userutil"
)
+
func (r RepoInfo) owner() string {
if r.OwnerHandle != "" {
return r.OwnerHandle
} else {
···
}
func (r RepoInfo) FullName() string {
+
return path.Join(r.owner(), r.Name)
}
func (r RepoInfo) OwnerWithoutAt() string {
+8 -7
appview/pages/templates/layouts/profilebase.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
{{ define "extrameta" }}
-
{{ $avatarUrl := fullAvatar .Card.UserHandle }}
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}" />
<meta property="og:type" content="profile" />
-
<meta property="og:url" content="https://tangled.org/{{ or .Card.UserHandle .Card.UserDid }}?tab={{ .Active }}" />
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
<meta property="og:image" content="{{ $avatarUrl }}" />
<meta property="og:image:width" content="512" />
<meta property="og:image:height" content="512" />
<meta name="twitter:card" content="summary" />
-
<meta name="twitter:title" content="{{ or .Card.UserHandle .Card.UserDid }}" />
-
<meta name="twitter:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
<meta name="twitter:image" content="{{ $avatarUrl }}" />
{{ end }}
···
+
{{ define "title" }}{{ resolve .Card.UserDid }}{{ end }}
{{ define "extrameta" }}
+
{{ $handle := resolve .Card.UserDid }}
+
{{ $avatarUrl := fullAvatar $handle }}
+
<meta property="og:title" content="{{ $handle }}" />
<meta property="og:type" content="profile" />
+
<meta property="og:url" content="https://tangled.org/{{ $handle }}?tab={{ .Active }}" />
+
<meta property="og:description" content="{{ or .Card.Profile.Description $handle }}" />
<meta property="og:image" content="{{ $avatarUrl }}" />
<meta property="og:image:width" content="512" />
<meta property="og:image:height" content="512" />
<meta name="twitter:card" content="summary" />
+
<meta name="twitter:title" content="{{ $handle }}" />
+
<meta name="twitter:description" content="{{ or .Card.Profile.Description $handle }}" />
<meta name="twitter:image" content="{{ $avatarUrl }}" />
{{ end }}
+1 -1
appview/pages/templates/repo/empty.html
···
<p><span class="{{$bullet}}">1</span>First, generate a new <a href="https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key" class="underline">SSH key pair</a>.</p>
<p><span class="{{$bullet}}">2</span>Then add the public key to your account from the <a href="/settings" class="underline">settings</a> page.</p>
-
<p><span class="{{$bullet}}">3</span>Configure your remote to <code>git@{{ $knot | stripPort }}:{{ .RepoInfo.OwnerHandle }}/{{ .RepoInfo.Name }}</code></p>
<p><span class="{{$bullet}}">4</span>Push!</p>
</div>
</div>
···
<p><span class="{{$bullet}}">1</span>First, generate a new <a href="https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key" class="underline">SSH key pair</a>.</p>
<p><span class="{{$bullet}}">2</span>Then add the public key to your account from the <a href="/settings" class="underline">settings</a> page.</p>
+
<p><span class="{{$bullet}}">3</span>Configure your remote to <code>git@{{ $knot | stripPort }}:{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}</code></p>
<p><span class="{{$bullet}}">4</span>Push!</p>
</div>
</div>
+3 -2
appview/pages/templates/repo/fragments/cloneDropdown.html
···
<!-- SSH Clone -->
<div class="mb-3">
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">SSH</label>
<div class="flex items-center border border-gray-300 dark:border-gray-600 rounded">
<code
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"
onclick="window.getSelection().selectAllChildren(this)"
-
data-url="git@{{ $knot | stripPort }}:{{ .RepoInfo.OwnerHandle }}/{{ .RepoInfo.Name }}"
-
>git@{{ $knot | stripPort }}:{{ .RepoInfo.OwnerHandle }}/{{ .RepoInfo.Name }}</code>
<button
onclick="copyToClipboard(this, this.previousElementSibling.getAttribute('data-url'))"
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"
···
<!-- SSH Clone -->
<div class="mb-3">
+
{{ $repoOwnerHandle := resolve .RepoInfo.OwnerDid }}
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">SSH</label>
<div class="flex items-center border border-gray-300 dark:border-gray-600 rounded">
<code
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"
onclick="window.getSelection().selectAllChildren(this)"
+
data-url="git@{{ $knot | stripPort }}:{{ $repoOwnerHandle }}/{{ .RepoInfo.Name }}"
+
>git@{{ $knot | stripPort }}:{{ $repoOwnerHandle }}/{{ .RepoInfo.Name }}</code>
<button
onclick="copyToClipboard(this, this.previousElementSibling.getAttribute('data-url'))"
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"
+5 -4
appview/pages/templates/repo/settings/access.html
···
{{ template "addCollaboratorButton" . }}
{{ end }}
{{ range .Collaborators }}
<div class="border border-gray-200 dark:border-gray-700 rounded p-4">
<div class="flex items-center gap-3">
<img
-
src="{{ fullAvatar .Handle }}"
-
alt="{{ .Handle }}"
class="rounded-full h-10 w-10 border border-gray-300 dark:border-gray-600 flex-shrink-0"/>
<div class="flex-1 min-w-0">
-
<a href="/{{ .Handle }}" class="block truncate">
-
{{ didOrHandle .Did .Handle }}
</a>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ .Role }}</p>
</div>
···
{{ template "addCollaboratorButton" . }}
{{ end }}
{{ range .Collaborators }}
+
{{ $handle := resolve .Did }}
<div class="border border-gray-200 dark:border-gray-700 rounded p-4">
<div class="flex items-center gap-3">
<img
+
src="{{ fullAvatar $handle }}"
+
alt="{{ $handle }}"
class="rounded-full h-10 w-10 border border-gray-300 dark:border-gray-600 flex-shrink-0"/>
<div class="flex-1 min-w-0">
+
<a href="/{{ $handle }}" class="block truncate">
+
{{ $handle }}
</a>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ .Role }}</p>
</div>
+6 -5
appview/pages/templates/strings/dashboard.html
···
-
{{ define "title" }}strings by {{ or .Card.UserHandle .Card.UserDid }}{{ end }}
{{ define "extrameta" }}
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}" />
<meta property="og:type" content="profile" />
-
<meta property="og:url" content="https://tangled.org/{{ or .Card.UserHandle .Card.UserDid }}" />
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
{{ end }}
···
{{ $s := index . 1 }}
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
<div class="font-medium dark:text-white flex gap-2 items-center">
-
<a href="/strings/{{ or $root.Card.UserHandle $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
</div>
{{ with $s.Description }}
<div class="text-gray-600 dark:text-gray-300 text-sm">
···
+
{{ define "title" }}strings by {{ resolve .Card.UserDid }}{{ end }}
{{ define "extrameta" }}
+
{{ $handle := resolve .Card.UserDid }}
+
<meta property="og:title" content="{{ $handle }}" />
<meta property="og:type" content="profile" />
+
<meta property="og:url" content="https://tangled.org/{{ $handle }}" />
+
<meta property="og:description" content="{{ or .Card.Profile.Description $handle }}" />
{{ end }}
···
{{ $s := index . 1 }}
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
<div class="font-medium dark:text-white flex gap-2 items-center">
+
<a href="/strings/{{ resolve $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
</div>
{{ with $s.Description }}
<div class="text-gray-600 dark:text-gray-300 text-sm">
+3 -3
appview/pages/templates/strings/string.html
···
-
{{ define "title" }}{{ .String.Filename }} · by {{ didOrHandle .Owner.DID.String .Owner.Handle.String }}{{ end }}
{{ define "extrameta" }}
-
{{ $ownerId := didOrHandle .Owner.DID.String .Owner.Handle.String }}
<meta property="og:title" content="{{ .String.Filename }} · by {{ $ownerId }}" />
<meta property="og:type" content="object" />
<meta property="og:url" content="https://tangled.org/strings/{{ $ownerId }}/{{ .String.Rkey }}" />
···
{{ end }}
{{ define "content" }}
-
{{ $ownerId := didOrHandle .Owner.DID.String .Owner.Handle.String }}
<section id="string-header" class="mb-4 py-2 px-6 dark:text-white">
<div class="text-lg flex items-center justify-between">
<div>
···
+
{{ define "title" }}{{ .String.Filename }} · by {{ resolve .Owner.DID.String }}{{ end }}
{{ define "extrameta" }}
+
{{ $ownerId := resolve .Owner.DID.String }}
<meta property="og:title" content="{{ .String.Filename }} · by {{ $ownerId }}" />
<meta property="og:type" content="object" />
<meta property="og:url" content="https://tangled.org/strings/{{ $ownerId }}/{{ .String.Rkey }}" />
···
{{ end }}
{{ define "content" }}
+
{{ $ownerId := resolve .Owner.DID.String }}
<section id="string-header" class="mb-4 py-2 px-6 dark:text-white">
<div class="text-lg flex items-center justify-between">
<div>
+1 -1
appview/pages/templates/user/followers.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · followers {{ end }}
{{ define "profileContent" }}
<div id="all-followers" class="md:col-span-8 order-2 md:order-2">
···
+
{{ define "title" }}{{ resolve .Card.UserDid }} · followers {{ end }}
{{ define "profileContent" }}
<div id="all-followers" class="md:col-span-8 order-2 md:order-2">
+1 -1
appview/pages/templates/user/following.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · following {{ end }}
{{ define "profileContent" }}
<div id="all-following" class="md:col-span-8 order-2 md:order-2">
···
+
{{ define "title" }}{{ resolve .Card.UserDid }} · following {{ end }}
{{ define "profileContent" }}
<div id="all-following" class="md:col-span-8 order-2 md:order-2">
+1 -1
appview/pages/templates/user/fragments/profileCard.html
···
{{ define "user/fragments/profileCard" }}
-
{{ $userIdent := didOrHandle .UserDid .UserHandle }}
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
<div id="avatar" class="col-span-1 flex justify-center items-center">
<div class="w-3/4 aspect-square relative">
···
{{ define "user/fragments/profileCard" }}
+
{{ $userIdent := resolve .UserDid }}
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
<div id="avatar" class="col-span-1 flex justify-center items-center">
<div class="w-3/4 aspect-square relative">
+2 -2
appview/pages/templates/user/overview.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
{{ define "profileContent" }}
<div id="all-repos" class="md:col-span-4 order-2 md:order-2">
···
{{ define "ownRepos" }}
<div>
<div class="text-sm font-bold px-2 pb-4 dark:text-white flex items-center gap-2">
-
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}?tab=repos"
class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
<span>PINNED REPOS</span>
</a>
···
+
{{ define "title" }}{{ resolve .Card.UserDid }}{{ end }}
{{ define "profileContent" }}
<div id="all-repos" class="md:col-span-4 order-2 md:order-2">
···
{{ define "ownRepos" }}
<div>
<div class="text-sm font-bold px-2 pb-4 dark:text-white flex items-center gap-2">
+
<a href="/{{ resolve $.Card.UserDid }}?tab=repos"
class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
<span>PINNED REPOS</span>
</a>
+1 -1
appview/pages/templates/user/repos.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }}
{{ define "profileContent" }}
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
···
+
{{ define "title" }}{{ resolve .Card.UserDid }} · repos {{ end }}
{{ define "profileContent" }}
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
+1 -1
appview/pages/templates/user/starred.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · repos {{ end }}
{{ define "profileContent" }}
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
···
+
{{ define "title" }}{{ resolve .Card.UserDid }} · repos {{ end }}
{{ define "profileContent" }}
<div id="all-repos" class="md:col-span-8 order-2 md:order-2">
+2 -2
appview/pages/templates/user/strings.html
···
-
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · strings {{ end }}
{{ define "profileContent" }}
<div id="all-strings" class="md:col-span-8 order-2 md:order-2">
···
{{ $s := index . 1 }}
<div class="py-4 px-6 rounded bg-white dark:bg-gray-800">
<div class="font-medium dark:text-white flex gap-2 items-center">
-
<a href="/strings/{{ or $root.Card.UserHandle $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
</div>
{{ with $s.Description }}
<div class="text-gray-600 dark:text-gray-300 text-sm">
···
+
{{ define "title" }}{{ resolve .Card.UserDid }} · strings {{ end }}
{{ define "profileContent" }}
<div id="all-strings" class="md:col-span-8 order-2 md:order-2">
···
{{ $s := index . 1 }}
<div class="py-4 px-6 rounded bg-white dark:bg-gray-800">
<div class="font-medium dark:text-white flex gap-2 items-center">
+
<a href="/strings/{{ resolve $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
</div>
{{ with $s.Description }}
<div class="text-gray-600 dark:text-gray-300 text-sm">
-14
appview/reporesolver/resolver.go
···
c := pages.Collaborator{
Did: did,
-
Handle: "",
Role: role,
}
collaborators = append(collaborators, c)
}
-
// populate all collborators with handles
-
identsToResolve := make([]string, len(collaborators))
-
for i, collab := range collaborators {
-
identsToResolve[i] = collab.Did
-
}
-
-
resolvedIdents := f.rr.idResolver.ResolveIdents(ctx, identsToResolve)
-
for i, resolved := range resolvedIdents {
-
if resolved != nil {
-
collaborators[i].Handle = resolved.Handle.String()
-
}
-
}
-
return collaborators, nil
}
···
c := pages.Collaborator{
Did: did,
Role: role,
}
collaborators = append(collaborators, c)
}
return collaborators, nil
}
+5 -6
appview/state/profile.go
···
return &pages.ProfileCard{
UserDid: did,
-
UserHandle: ident.Handle.String(),
Profile: profile,
FollowStatus: followStatus,
Stats: pages.ProfileStats{
···
s.pages.Error500(w)
return
}
-
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
repos, err := db.GetRepos(
s.db,
···
s.pages.Error500(w)
return
}
-
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
repos, err := db.GetRepos(
s.db,
···
s.pages.Error500(w)
return
}
-
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
stars, err := db.GetStars(s.db, 0, db.FilterEq("starred_by_did", profile.UserDid))
if err != nil {
···
s.pages.Error500(w)
return
}
-
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid))
if err != nil {
···
if err != nil {
return nil, err
}
-
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
loggedInUser := s.oauth.GetUser(r)
params := FollowsPageParams{
···
return &pages.ProfileCard{
UserDid: did,
Profile: profile,
FollowStatus: followStatus,
Stats: pages.ProfileStats{
···
s.pages.Error500(w)
return
}
+
l = l.With("profileDid", profile.UserDid)
repos, err := db.GetRepos(
s.db,
···
s.pages.Error500(w)
return
}
+
l = l.With("profileDid", profile.UserDid)
repos, err := db.GetRepos(
s.db,
···
s.pages.Error500(w)
return
}
+
l = l.With("profileDid", profile.UserDid)
stars, err := db.GetStars(s.db, 0, db.FilterEq("starred_by_did", profile.UserDid))
if err != nil {
···
s.pages.Error500(w)
return
}
+
l = l.With("profileDid", profile.UserDid)
strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid))
if err != nil {
···
if err != nil {
return nil, err
}
+
l = l.With("profileDid", profile.UserDid)
loggedInUser := s.oauth.GetUser(r)
params := FollowsPageParams{