forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package state
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/hex"
7 "fmt"
8 "log"
9 "net/http"
10
11 "github.com/bluesky-social/indigo/atproto/identity"
12 "github.com/go-chi/chi/v5"
13 "go.opentelemetry.io/otel/attribute"
14 "tangled.sh/tangled.sh/core/appview/db"
15 "tangled.sh/tangled.sh/core/appview/pages"
16)
17
18func (s *State) ProfilePage(w http.ResponseWriter, r *http.Request) {
19 ctx, span := s.t.TraceStart(r.Context(), "ProfilePage")
20 defer span.End()
21
22 didOrHandle := chi.URLParam(r, "user")
23 if didOrHandle == "" {
24 http.Error(w, "Bad request", http.StatusBadRequest)
25 return
26 }
27
28 ident, ok := ctx.Value("resolvedId").(identity.Identity)
29 if !ok {
30 s.pages.Error404(w)
31 span.RecordError(fmt.Errorf("failed to resolve identity"))
32 return
33 }
34
35 span.SetAttributes(
36 attribute.String("user.did", ident.DID.String()),
37 attribute.String("user.handle", ident.Handle.String()),
38 )
39
40 repos, err := db.GetAllReposByDid(ctx, s.db, ident.DID.String())
41 if err != nil {
42 log.Printf("getting repos for %s: %s", ident.DID.String(), err)
43 span.RecordError(err)
44 span.SetAttributes(attribute.String("error.repos", err.Error()))
45 }
46 span.SetAttributes(attribute.Int("repos.count", len(repos)))
47
48 collaboratingRepos, err := db.CollaboratingIn(ctx, s.db, ident.DID.String())
49 if err != nil {
50 log.Printf("getting collaborating repos for %s: %s", ident.DID.String(), err)
51 span.RecordError(err)
52 span.SetAttributes(attribute.String("error.collaborating_repos", err.Error()))
53 }
54 span.SetAttributes(attribute.Int("collaborating_repos.count", len(collaboratingRepos)))
55
56 timeline, err := db.MakeProfileTimeline(ctx, s.db, ident.DID.String())
57 if err != nil {
58 log.Printf("failed to create profile timeline for %s: %s", ident.DID.String(), err)
59 span.RecordError(err)
60 span.SetAttributes(attribute.String("error.timeline", err.Error()))
61 }
62
63 var didsToResolve []string
64 for _, r := range collaboratingRepos {
65 didsToResolve = append(didsToResolve, r.Did)
66 }
67 for _, byMonth := range timeline.ByMonth {
68 for _, pe := range byMonth.PullEvents.Items {
69 didsToResolve = append(didsToResolve, pe.Repo.Did)
70 }
71 for _, ie := range byMonth.IssueEvents.Items {
72 didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did)
73 }
74 for _, re := range byMonth.RepoEvents {
75 didsToResolve = append(didsToResolve, re.Repo.Did)
76 if re.Source != nil {
77 didsToResolve = append(didsToResolve, re.Source.Did)
78 }
79 }
80 }
81 span.SetAttributes(attribute.Int("dids_to_resolve.count", len(didsToResolve)))
82
83 resolvedIds := s.resolver.ResolveIdents(ctx, didsToResolve)
84 didHandleMap := make(map[string]string)
85 for _, identity := range resolvedIds {
86 if !identity.Handle.IsInvalidHandle() {
87 didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
88 } else {
89 didHandleMap[identity.DID.String()] = identity.DID.String()
90 }
91 }
92 span.SetAttributes(attribute.Int("resolved_ids.count", len(resolvedIds)))
93
94 followers, following, err := db.GetFollowerFollowing(s.db, ident.DID.String())
95 if err != nil {
96 log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
97 span.RecordError(err)
98 span.SetAttributes(attribute.String("error.follow_stats", err.Error()))
99 }
100 span.SetAttributes(
101 attribute.Int("followers.count", followers),
102 attribute.Int("following.count", following),
103 )
104
105 loggedInUser := s.auth.GetUser(r)
106 followStatus := db.IsNotFollowing
107 if loggedInUser != nil {
108 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String())
109 span.SetAttributes(attribute.String("logged_in_user.did", loggedInUser.Did))
110 }
111 span.SetAttributes(attribute.String("follow_status", string(db.FollowStatus(followStatus))))
112
113 profileAvatarUri := s.GetAvatarUri(ident.Handle.String())
114 s.pages.ProfilePage(w, pages.ProfilePageParams{
115 LoggedInUser: loggedInUser,
116 UserDid: ident.DID.String(),
117 UserHandle: ident.Handle.String(),
118 Repos: repos,
119 CollaboratingRepos: collaboratingRepos,
120 ProfileStats: pages.ProfileStats{
121 Followers: followers,
122 Following: following,
123 },
124 FollowStatus: db.FollowStatus(followStatus),
125 DidHandleMap: didHandleMap,
126 AvatarUri: profileAvatarUri,
127 ProfileTimeline: timeline,
128 })
129}
130
131func (s *State) GetAvatarUri(handle string) string {
132 secret := s.config.AvatarSharedSecret
133 h := hmac.New(sha256.New, []byte(secret))
134 h.Write([]byte(handle))
135 signature := hex.EncodeToString(h.Sum(nil))
136 return fmt.Sprintf("%s/%s/%s", s.config.AvatarHost, signature, handle)
137}