Monorepo for Tangled
tangled.org
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 // find the record in the db
130 star, err := db.GetStar(s.db, currentUser.Did, subjectKey)
131 if err != nil {
132 l.Error("failed to get star relationship", "err", err)
133 return
134 }
135
136 _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
137 Collection: tangled.FeedStarNSID,
138 Repo: currentUser.Did,
139 Rkey: star.Rkey,
140 })
141
142 if err != nil {
143 l.Error("failed to unstar", "err", err)
144 return
145 }
146
147 err = db.DeleteStarByRkey(s.db, currentUser.Did, star.Rkey)
148 if err != nil {
149 l.Warn("failed to delete star from DB", "err", err)
150 // this is not an issue, the firehose event might have already done this
151 }
152
153 starCount, err := db.GetStarCount(s.db, subjectType, subjectKey)
154 if err != nil {
155 l.Error("failed to get star count", "subject", subjectKey, "err", err)
156 return
157 }
158
159 s.notifier.DeleteStar(r.Context(), star)
160
161 s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{
162 IsStarred: false,
163 SubjectAt: subjectUri,
164 StarCount: starCount,
165 RepoName: repoName,
166 })
167
168 return
169 }
170}