tiny 88x31 lexicon for atproto
1package handler
2
3import (
4 "fmt"
5 "log"
6 "net/http"
7 "os"
8 "strings"
9
10 "github.com/bluesky-social/indigo/atproto/auth/oauth"
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "tangled.org/moth11.net/88x31/blobs"
13 "tangled.org/moth11.net/88x31/lex"
14 myoauth "tangled.org/moth11.net/88x31/oauth"
15 "tangled.org/moth11.net/88x31/types"
16)
17
18func (h *Handler) like(cs *oauth.ClientSession, w http.ResponseWriter, r *http.Request) {
19 if cs == nil {
20 http.Error(w, "like requires auth", http.StatusUnauthorized)
21 return
22 }
23 err := r.ParseForm()
24 if err != nil {
25 http.Error(w, "form failed to parse", http.StatusBadRequest)
26 return
27 }
28 uri := r.FormValue("uri")
29 if uri == "" {
30 http.Error(w, "must provide uri, don't tamper with form", http.StatusBadRequest)
31 return
32 }
33 cid := r.FormValue("cid")
34 if cid == "" {
35 http.Error(w, "must provide a cid, don't tamper with form", http.StatusBadRequest)
36 return
37 }
38 var llr lex.LikeRecord
39 lls := lex.LikeSubject{URI: uri, CID: cid}
40 llr.Subject = lls
41 nowsyn := syntax.DatetimeNow()
42 llr.CreatedAt = nowsyn.String()
43 luri, lcid, err := myoauth.CreateLike(cs, &llr, r.Context())
44 if err != nil {
45 log.Println(err)
46 http.Error(w, "error creating post", http.StatusInternalServerError)
47 return
48 }
49 var like types.Like
50 like.CID = lcid
51 like.URI = luri
52 like.CreatedAt = nowsyn.Time()
53 like.SubjectCID = cid
54 like.SubjectURI = uri
55 like.DID = cs.Data.AccountDID.String()
56 err = h.db.StoreLike(&like, r.Context())
57 if err != nil {
58 log.Println(err)
59 http.Error(w, "error storing like", http.StatusInternalServerError)
60 return
61 }
62 http.Redirect(w, r, fmt.Sprintf("/button?uri=%s", uri), http.StatusSeeOther)
63}
64
65func (h *Handler) unlike(cs *oauth.ClientSession, w http.ResponseWriter, r *http.Request) {
66 if cs == nil {
67 http.Error(w, "unlike requires auth", http.StatusUnauthorized)
68 return
69 }
70 err := r.ParseForm()
71 if err != nil {
72 http.Error(w, "form failed to parse", http.StatusBadRequest)
73 return
74 }
75 uri := r.FormValue("uri")
76 if uri == "" {
77 http.Error(w, "must provide uri, don't tamper with form", http.StatusBadRequest)
78 return
79 }
80 cid := r.FormValue("cid")
81 if cid == "" {
82 http.Error(w, "must provide a cid, don't tamper with form", http.StatusBadRequest)
83 return
84 }
85
86 ll, err := h.db.GetMyLikesFor(uri, cid, cs.Data.AccountDID.String(), r.Context())
87 if err != nil {
88 log.Println(err)
89 http.Redirect(w, r, fmt.Sprintf("/button?uri=%s", uri), http.StatusSeeOther)
90 return
91 }
92 for _, likeuricid := range ll {
93 arr := strings.Split(likeuricid, " ")
94 if len(arr) != 2 {
95 http.Error(w, "invalid likeuricid", http.StatusInternalServerError)
96 return
97 }
98 likeuri := arr[0]
99 likecid := arr[1]
100 aturi, err := syntax.ParseATURI(likeuri)
101 if err != nil {
102 http.Error(w, "uri doesn't parse, don't tamper with form", http.StatusBadRequest)
103 return
104 }
105 err = myoauth.DeleteLike(cs, aturi.RecordKey().String(), likecid, r.Context())
106 if err != nil {
107 log.Println(err)
108 http.Error(w, "error deleting like", http.StatusInternalServerError)
109 return
110 }
111 err = h.db.DeleteLike(likeuri, r.Context())
112 if err != nil {
113 log.Println(err)
114 http.Error(w, "error deleting cached like", http.StatusInternalServerError)
115 return
116 }
117 }
118 http.Redirect(w, r, fmt.Sprintf("/button?uri=%s", uri), http.StatusSeeOther)
119}
120
121func (h *Handler) delete(cs *oauth.ClientSession, w http.ResponseWriter, r *http.Request) {
122 if cs == nil {
123 http.Error(w, "deletion requires auth", http.StatusUnauthorized)
124 return
125 }
126 err := r.ParseForm()
127 if err != nil {
128 http.Error(w, "form failed to parse", http.StatusBadRequest)
129 return
130 }
131 uri := r.FormValue("uri")
132 if uri == "" {
133 http.Error(w, "must provide uri, don't tamper with form", http.StatusBadRequest)
134 return
135 }
136 aturi, err := syntax.ParseATURI(uri)
137 if err != nil {
138 http.Error(w, "uri should parse", http.StatusBadRequest)
139 return
140 }
141 cid := r.FormValue("cid")
142 if cid == "" {
143 http.Error(w, "must provide a cid, don't tamper with form", http.StatusBadRequest)
144 return
145 }
146 ll, err := h.db.GetMyLikesFor(uri, cid, cs.Data.AccountDID.String(), r.Context())
147 if err == nil {
148 for _, likeuricid := range ll {
149 arr := strings.Split(likeuricid, " ")
150 if len(arr) != 2 {
151 http.Error(w, "invalid likeuricid", http.StatusInternalServerError)
152 return
153 }
154 likeuri := arr[0]
155 likecid := arr[1]
156 aturi, err := syntax.ParseATURI(likeuri)
157 if err != nil {
158 http.Error(w, "uri doesn't parse, don't tamper with form", http.StatusBadRequest)
159 return
160 }
161 err = myoauth.DeleteLike(cs, aturi.RecordKey().String(), likecid, r.Context())
162 if err != nil {
163 log.Println(err)
164 http.Error(w, "error deleting like", http.StatusInternalServerError)
165 return
166 }
167 err = h.db.DeleteLike(likeuri, r.Context())
168 if err != nil {
169 log.Println(err)
170 http.Error(w, "error deleting cached like", http.StatusInternalServerError)
171 return
172 }
173 }
174 }
175 err = myoauth.DeleteButton(cs, aturi.RecordKey().String(), cid, r.Context())
176 if err != nil {
177 log.Println(err)
178 http.Error(w, "failed to delete button", http.StatusInternalServerError)
179 return
180 }
181 err = h.db.DeleteButton(uri, r.Context())
182 if err != nil {
183 log.Println(err)
184 http.Error(w, "error deleting cached button", http.StatusInternalServerError)
185 return
186 }
187 http.Redirect(w, r, "/", http.StatusSeeOther)
188}
189
190func (h *Handler) upload(cs *oauth.ClientSession, w http.ResponseWriter, r *http.Request) {
191 if cs == nil {
192 http.Error(w, "upload requires auth", http.StatusUnauthorized)
193 return
194 }
195 err := r.ParseMultipartForm(1 << 21)
196 if err != nil {
197 http.Error(w, "form failed to parse", http.StatusBadRequest)
198 return
199 }
200 alt := r.FormValue("alt")
201 title := r.FormValue("title")
202 href := r.FormValue("href")
203
204 file, fheader, err := r.FormFile("button")
205 if err != nil {
206 http.Error(w, "form lacks button", http.StatusBadRequest)
207 return
208 }
209 defer file.Close()
210 ct := fheader.Header.Get("Content-Type")
211 if !strings.HasPrefix(ct, "image/") {
212 http.Error(w, "button must be image", http.StatusBadRequest)
213 return
214 }
215 blob, err := myoauth.UploadBLOB(cs, file, fheader, r.Context())
216 if err != nil {
217 http.Error(w, "failed to upload blob", http.StatusInternalServerError)
218 return
219 }
220 var lbr lex.ButtonRecord
221 if blob == nil {
222 http.Error(w, "recieved nil blob", http.StatusInternalServerError)
223 return
224 }
225 lbr.Blob = *blob
226 if alt != "" {
227 lbr.Alt = &alt
228 }
229 if title != "" {
230 lbr.Title = &title
231 }
232 if href != "" {
233 lbr.Href = &href
234 }
235 nowsyn := syntax.DatetimeNow()
236 lbr.PostedAt = nowsyn.String()
237 uri, cid, err := myoauth.CreateButton(cs, &lbr, r.Context())
238 if err != nil {
239 log.Println(err)
240 http.Error(w, "error creating button", http.StatusInternalServerError)
241 return
242 }
243 var tbr types.Button
244 tbr.Alt = lbr.Alt
245 tbr.Title = lbr.Title
246 tbr.HREF = lbr.Href
247 tbr.BlobCID = blob.Ref.String()
248 tbr.BlobMIME = ct
249 tbr.URI = uri
250 tbr.CID = cid
251 tbr.DID = cs.Data.AccountDID.String()
252 tbr.PostedAt = nowsyn.Time()
253 err = h.db.StoreButton(&tbr, r.Context())
254 if err != nil {
255 log.Println(err)
256 }
257 http.Redirect(w, r, fmt.Sprintf("/button?uri=%s", uri), http.StatusFound)
258}
259
260func (h *Handler) getButton(w http.ResponseWriter, r *http.Request) {
261 vals := r.URL.Query()
262 var did string
263 var cid string
264 uri := vals.Get("uri")
265 var button *types.Button
266 var err error
267 if uri != "" {
268 button, err = h.db.GetButton(uri, r.Context())
269 if err == nil {
270 did = button.DID
271 cid = button.BlobCID
272 }
273 } else {
274 http.Error(w, "provide a uri!", http.StatusBadRequest)
275 return
276 }
277 ib, _ := h.db.IsBanned(did, r.Context())
278 if ib {
279 http.Error(w, "i don't serve banned content", 404)
280 return
281 }
282 imgPath, err := blobs.AddImageToCache(did, cid, r.Context())
283 if err != nil {
284 http.Error(w, "error adding to cache, likely bad size", http.StatusInternalServerError)
285 return
286 }
287
288 stats, err := os.Stat(imgPath)
289 if err != nil {
290 http.Error(w, "error recieving from cache, strange...", http.StatusInternalServerError)
291 return
292 }
293
294 mime := button.BlobMIME
295 w.Header().Add("Content-Type", mime)
296 w.Header().Add("Content-Length", fmt.Sprintf("%d", stats.Size()))
297 w.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
298
299 img, err := os.Open(imgPath)
300 if err != nil {
301 http.Error(w, "error reading from cache, strange...", http.StatusInternalServerError)
302 return
303 }
304 img.WriteTo(w)
305}