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}