forked from
tangled.org/core
Monorepo for Tangled
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 createdAt := time.Now().Format(time.RFC3339)
81 rkey := tid.TID()
82
83 starRecord := &tangled.FeedStar{
84 CreatedAt: createdAt,
85 Subject: starSubject,
86 }
87
88 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
89 Collection: tangled.FeedStarNSID,
90 Repo: currentUser.Did,
91 Rkey: rkey,
92 Record: &lexutil.LexiconTypeDecoder{Val: starRecord},
93 })
94 if err != nil {
95 l.Error("failed to create atproto record", "err", err)
96 return
97 }
98 l.Info("created atproto record", "uri", resp.Uri)
99
100 star := &models.Star{
101 Did: currentUser.Did,
102 SubjectType: subjectType,
103 Subject: subjectKey,
104 Rkey: rkey,
105 }
106
107 err = db.AddStar(s.db, star)
108 if err != nil {
109 l.Error("failed to star", "err", err)
110 return
111 }
112
113 starCount, err := db.GetStarCount(s.db, subjectType, subjectKey)
114 if err != nil {
115 l.Error("failed to get star count", "subject", subjectKey, "err", err)
116 }
117
118 s.notifier.NewStar(r.Context(), star)
119
120 s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{
121 IsStarred: true,
122 SubjectAt: subjectUri,
123 StarCount: starCount,
124 RepoName: repoName,
125 })
126
127 return
128 case http.MethodDelete:
129 tx, err := s.db.BeginTx(r.Context(), nil)
130 if err != nil {
131 l.Error("failed to start transaction", "err", err)
132 }
133 defer tx.Rollback()
134
135 stars, err := db.DeleteStars(tx, syntax.DID(currentUser.Did), subjectKey)
136 if err != nil {
137 l.Error("failed to delete stars from db", "err", err)
138 return
139 }
140
141 var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
142 for _, starAt := range stars {
143 writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
144 RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
145 Collection: tangled.FeedStarNSID,
146 Rkey: starAt.RecordKey().String(),
147 },
148 })
149 }
150 _, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
151 Repo: currentUser.Did,
152 Writes: writes,
153 })
154 if err != nil {
155 l.Error("failed to delete stars from PDS", "err", err)
156 return
157 }
158
159 if err := tx.Commit(); err != nil {
160 l.Error("failed to commit transaction", "err", err)
161 // DB op failed but record is created in PDS. Ingester will backfill the missed operation
162 }
163
164 s.notifier.DeleteStar(r.Context(), &models.Star{
165 Did: currentUser.Did,
166 SubjectType: subjectType,
167 Subject: subjectKey,
168 // Rkey
169 // Created
170 })
171
172 starCount, err := db.GetStarCount(s.db, subjectType, subjectKey)
173 if err != nil {
174 l.Error("failed to get star count", "subject", subjectKey, "err", err)
175 return
176 }
177
178 s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{
179 IsStarred: false,
180 SubjectAt: subjectUri,
181 StarCount: starCount,
182 RepoName: repoName,
183 })
184
185 return
186 }
187}