Monorepo for Tangled tangled.org
6

Configure Feed

Select the types of activity you want to include in your feed.

1package state 2 3import ( 4 "fmt" 5 "log" 6 "net/http" 7 "time" 8 9 comatproto "github.com/bluesky-social/indigo/api/atproto" 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 lexutil "github.com/bluesky-social/indigo/lex/util" 12 "tangled.org/core/api/tangled" 13 "tangled.org/core/appview/db" 14 "tangled.org/core/appview/models" 15 "tangled.org/core/appview/pages" 16 "tangled.org/core/tid" 17) 18 19func resolveStarSubject(d db.Execer, subjectUri syntax.ATURI) (models.StarSubjectType, string, *tangled.FeedStar_Subject, error) { 20 collection := subjectUri.Collection() 21 22 switch collection.String() { 23 case tangled.RepoNSID: 24 repo, err := db.GetRepoByAtUri(d, subjectUri.String()) 25 if err != nil { 26 return "", "", nil, err 27 } 28 if repo.RepoDid == "" { 29 return "", "", nil, fmt.Errorf("repo has no DID: %s", subjectUri) 30 } 31 subject := &tangled.FeedStar_Subject{ 32 FeedStar_Repo: &tangled.FeedStar_Repo{Did: repo.RepoDid}, 33 } 34 return models.StarSubjectRepo, repo.RepoDid, subject, nil 35 36 case tangled.StringNSID: 37 uri := subjectUri.String() 38 subject := &tangled.FeedStar_Subject{ 39 FeedStar_String: &tangled.FeedStar_String{Uri: uri}, 40 } 41 return models.StarSubjectString, uri, subject, nil 42 43 default: 44 return "", "", nil, fmt.Errorf("unsupported star subject collection: %s", collection) 45 } 46} 47 48func (s *State) Star(w http.ResponseWriter, r *http.Request) { 49 l := s.logger.With("handler", "Star") 50 currentUser := s.oauth.GetMultiAccountUser(r) 51 52 subject := r.URL.Query().Get("subject") 53 if subject == "" { 54 l.Warn("invalid form") 55 return 56 } 57 58 subjectUri, err := syntax.ParseATURI(subject) 59 if err != nil { 60 l.Warn("invalid form", "subject", subject, "err", err) 61 return 62 } 63 64 subjectType, subjectKey, starSubject, err := resolveStarSubject(s.db, subjectUri) 65 if err != nil { 66 log.Println("failed to resolve star subject", err) 67 return 68 } 69 70 client, err := s.oauth.AuthorizedClient(r) 71 if err != nil { 72 l.Error("failed to authorize client", "err", err) 73 return 74 } 75 76 repoName := r.URL.Query().Get("repoName") 77 78 switch r.Method { 79 case http.MethodPost: 80 star := models.Star{ 81 Did: currentUser.Did, 82 Rkey: tid.TID(), 83 SubjectType: subjectType, 84 Subject: subjectKey, 85 Created: time.Now(), 86 } 87 88 tx, err := s.db.BeginTx(r.Context(), nil) 89 if err != nil { 90 l.Error("failed to start transaction", "err", err) 91 return 92 } 93 defer tx.Rollback() 94 95 if err := db.UpsertStar(tx, star); err != nil { 96 l.Error("failed to star", "err", err) 97 return 98 } 99 100 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 101 Collection: tangled.FeedStarNSID, 102 Repo: currentUser.Did, 103 Rkey: star.Rkey, 104 Record: &lexutil.LexiconTypeDecoder{ 105 Val: &tangled.FeedStar{ 106 CreatedAt: star.Created.Format(time.RFC3339), 107 Subject: starSubject, 108 }, 109 }, 110 }) 111 if err != nil { 112 l.Error("failed to create atproto record", "err", err) 113 return 114 } 115 l.Info("created atproto record", "uri", resp.Uri) 116 117 if err := tx.Commit(); err != nil { 118 l.Error("failed to commit transaction", "err", err) 119 // DB op failed but record is created in PDS. Ingester will backfill the missed operation 120 } 121 122 s.notifier.NewStar(r.Context(), &star) 123 124 starCount, err := db.GetStarCount(s.db, subjectType, subjectKey) 125 if err != nil { 126 l.Error("failed to get star count", "subject", subjectKey, "err", err) 127 } 128 129 s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{ 130 IsStarred: true, 131 SubjectAt: subjectUri, 132 StarCount: starCount, 133 RepoName: repoName, 134 }) 135 136 return 137 case http.MethodDelete: 138 tx, err := s.db.BeginTx(r.Context(), nil) 139 if err != nil { 140 l.Error("failed to start transaction", "err", err) 141 } 142 defer tx.Rollback() 143 144 stars, err := db.DeleteStars(tx, syntax.DID(currentUser.Did), subjectKey) 145 if err != nil { 146 l.Error("failed to delete stars from db", "err", err) 147 return 148 } 149 150 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem 151 for _, starAt := range stars { 152 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{ 153 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{ 154 Collection: tangled.FeedStarNSID, 155 Rkey: starAt.RecordKey().String(), 156 }, 157 }) 158 } 159 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{ 160 Repo: currentUser.Did, 161 Writes: writes, 162 }) 163 if err != nil { 164 l.Error("failed to delete stars from PDS", "err", err) 165 return 166 } 167 168 if err := tx.Commit(); err != nil { 169 l.Error("failed to commit transaction", "err", err) 170 // DB op failed but record is created in PDS. Ingester will backfill the missed operation 171 } 172 173 s.notifier.DeleteStar(r.Context(), &models.Star{ 174 Did: currentUser.Did, 175 SubjectType: subjectType, 176 Subject: subjectKey, 177 // Rkey 178 // Created 179 }) 180 181 starCount, err := db.GetStarCount(s.db, subjectType, subjectKey) 182 if err != nil { 183 l.Error("failed to get star count", "subject", subjectKey, "err", err) 184 return 185 } 186 187 s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{ 188 IsStarred: false, 189 SubjectAt: subjectUri, 190 StarCount: starCount, 191 RepoName: repoName, 192 }) 193 194 return 195 } 196}