database.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "crypto/sha512"
21 "database/sql"
22 _ "embed"
23 "encoding/json"
24 "fmt"
25 "html/template"
26 "sort"
27 "strconv"
28 "strings"
29 "sync"
30 "time"
31
32 "humungus.tedunangst.com/r/webs/cache"
33 "humungus.tedunangst.com/r/webs/httpsig"
34 "humungus.tedunangst.com/r/webs/login"
35 "humungus.tedunangst.com/r/webs/mz"
36)
37
38const honkwindow time.Duration = 90 * 24 * time.Hour
39
40//go:embed schema.sql
41var sqlSchema string
42
43func userfromrow(row *sql.Row) (*WhatAbout, error) {
44 user := new(WhatAbout)
45 var seckey, options string
46 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &seckey, &options)
47 if err == nil {
48 user.SecKey, _, err = httpsig.DecodeKey(seckey)
49 }
50 if err != nil {
51 return nil, err
52 }
53 if user.ID > 0 {
54 user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
55 err = unjsonify(options, &user.Options)
56 if err != nil {
57 elog.Printf("error processing user options: %s", err)
58 }
59 } else {
60 user.URL = fmt.Sprintf("https://%s/%s", serverName, user.Name)
61 }
62 if user.Options.Reaction == "" {
63 user.Options.Reaction = "none"
64 }
65
66 return user, nil
67}
68
69var somenamedusers = cache.New(cache.Options{Filler: func(name string) (*WhatAbout, bool) {
70 row := stmtUserByName.QueryRow(name)
71 user, err := userfromrow(row)
72 if err != nil {
73 return nil, false
74 }
75 var marker mz.Marker
76 marker.HashLinker = ontoreplacer
77 marker.AtLinker = attoreplacer
78 user.HTAbout = template.HTML(marker.Mark(user.About))
79 user.Onts = marker.HashTags
80 return user, true
81}})
82
83var somenumberedusers = cache.New(cache.Options{Filler: func(userid int64) (*WhatAbout, bool) {
84 row := stmtUserByNumber.QueryRow(userid)
85 user, err := userfromrow(row)
86 if err != nil {
87 return nil, false
88 }
89 // don't touch attoreplacer, which introduces a loop
90 // finger -> getjunk -> keys -> users
91 return user, true
92}})
93
94func getserveruser() *WhatAbout {
95 var user *WhatAbout
96 ok := somenumberedusers.Get(serverUID, &user)
97 if !ok {
98 elog.Panicf("lost server user")
99 }
100 return user
101}
102
103func butwhatabout(name string) (*WhatAbout, error) {
104 var user *WhatAbout
105 ok := somenamedusers.Get(name, &user)
106 if !ok {
107 return nil, fmt.Errorf("no user: %s", name)
108 }
109 return user, nil
110}
111
112var honkerinvalidator cache.Invalidator
113
114func gethonkers(userid int64) []*Honker {
115 rows, err := stmtHonkers.Query(userid)
116 if err != nil {
117 elog.Printf("error querying honkers: %s", err)
118 return nil
119 }
120 defer rows.Close()
121 var honkers []*Honker
122 for rows.Next() {
123 h := new(Honker)
124 var combos, meta string
125 err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos, &meta)
126 if err == nil {
127 err = unjsonify(meta, &h.Meta)
128 }
129 if err != nil {
130 elog.Printf("error scanning honker: %s", err)
131 continue
132 }
133 h.Combos = strings.Split(strings.TrimSpace(combos), " ")
134 honkers = append(honkers, h)
135 }
136 return honkers
137}
138
139func getdubs(userid int64) []*Honker {
140 rows, err := stmtDubbers.Query(userid)
141 return dubsfromrows(rows, err)
142}
143
144func getnameddubs(userid int64, name string) []*Honker {
145 rows, err := stmtNamedDubbers.Query(userid, name)
146 return dubsfromrows(rows, err)
147}
148
149func dubsfromrows(rows *sql.Rows, err error) []*Honker {
150 if err != nil {
151 elog.Printf("error querying dubs: %s", err)
152 return nil
153 }
154 defer rows.Close()
155 var honkers []*Honker
156 for rows.Next() {
157 h := new(Honker)
158 err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor)
159 if err != nil {
160 elog.Printf("error scanning honker: %s", err)
161 return nil
162 }
163 honkers = append(honkers, h)
164 }
165 return honkers
166}
167
168func allusers() []login.UserInfo {
169 var users []login.UserInfo
170 rows, _ := opendatabase().Query("select userid, username from users where userid > 0")
171 defer rows.Close()
172 for rows.Next() {
173 var u login.UserInfo
174 rows.Scan(&u.UserID, &u.Username)
175 users = append(users, u)
176 }
177 return users
178}
179
180func getxonk(userid int64, xid string) *Honk {
181 row := stmtOneXonk.QueryRow(userid, xid)
182 return scanhonk(row)
183}
184
185func getbonk(userid int64, xid string) *Honk {
186 row := stmtOneBonk.QueryRow(userid, xid)
187 return scanhonk(row)
188}
189
190func getpublichonks() []*Honk {
191 dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
192 rows, err := stmtPublicHonks.Query(dt, 100)
193 return getsomehonks(rows, err)
194}
195func geteventhonks(userid int64) []*Honk {
196 rows, err := stmtEventHonks.Query(userid, 25)
197 honks := getsomehonks(rows, err)
198 sort.Slice(honks, func(i, j int) bool {
199 var t1, t2 time.Time
200 if honks[i].Time == nil {
201 t1 = honks[i].Date
202 } else {
203 t1 = honks[i].Time.StartTime
204 }
205 if honks[j].Time == nil {
206 t2 = honks[j].Date
207 } else {
208 t2 = honks[j].Time.StartTime
209 }
210 return t1.After(t2)
211 })
212 now := time.Now().Add(-24 * time.Hour)
213 for i, h := range honks {
214 t := h.Date
215 if tm := h.Time; tm != nil {
216 t = tm.StartTime
217 }
218 if t.Before(now) {
219 honks = honks[:i]
220 break
221 }
222 }
223 reversehonks(honks)
224 return honks
225}
226func gethonksbyuser(name string, includeprivate bool, wanted int64) []*Honk {
227 dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
228 limit := 50
229 whofore := 2
230 if includeprivate {
231 whofore = 3
232 }
233 rows, err := stmtUserHonks.Query(wanted, whofore, name, dt, limit)
234 return getsomehonks(rows, err)
235}
236func gethonksforuser(userid int64, wanted int64) []*Honk {
237 dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
238 rows, err := stmtHonksForUser.Query(wanted, userid, dt, userid, userid)
239 return getsomehonks(rows, err)
240}
241func gethonksforuserfirstclass(userid int64, wanted int64) []*Honk {
242 dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
243 rows, err := stmtHonksForUserFirstClass.Query(wanted, userid, dt, userid, userid)
244 return getsomehonks(rows, err)
245}
246
247func gethonksforme(userid int64, wanted int64) []*Honk {
248 dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
249 rows, err := stmtHonksForMe.Query(wanted, userid, dt, userid)
250 return getsomehonks(rows, err)
251}
252func gethonksfromlongago(userid int64, wanted int64) []*Honk {
253 now := time.Now()
254 var honks []*Honk
255 for i := 1; i <= 4; i++ {
256 dt := time.Date(now.Year()-i, now.Month(), now.Day(), now.Hour(), now.Minute(),
257 now.Second(), 0, now.Location())
258 dt1 := dt.Add(-36 * time.Hour).UTC().Format(dbtimeformat)
259 dt2 := dt.Add(12 * time.Hour).UTC().Format(dbtimeformat)
260 rows, err := stmtHonksFromLongAgo.Query(wanted, userid, dt1, dt2, userid)
261 honks = append(honks, getsomehonks(rows, err)...)
262 }
263 return honks
264}
265func getsavedhonks(userid int64, wanted int64) []*Honk {
266 rows, err := stmtHonksISaved.Query(wanted, userid)
267 return getsomehonks(rows, err)
268}
269func gethonksbyhonker(userid int64, honker string, wanted int64) []*Honk {
270 rows, err := stmtHonksByHonker.Query(wanted, userid, honker, userid)
271 return getsomehonks(rows, err)
272}
273func gethonksbyxonker(userid int64, xonker string, wanted int64) []*Honk {
274 rows, err := stmtHonksByXonker.Query(wanted, userid, xonker, xonker, userid)
275 return getsomehonks(rows, err)
276}
277func gethonksbycombo(userid int64, combo string, wanted int64) []*Honk {
278 combo = "% " + combo + " %"
279 rows, err := stmtHonksByCombo.Query(wanted, userid, userid, combo, userid, wanted, userid, combo, userid)
280 return getsomehonks(rows, err)
281}
282func gethonksbyconvoy(userid int64, convoy string, wanted int64) []*Honk {
283 rows, err := stmtHonksByConvoy.Query(wanted, userid, userid, convoy)
284 honks := getsomehonks(rows, err)
285 return honks
286}
287func gethonksbysearch(userid int64, q string, wanted int64) []*Honk {
288 var queries []string
289 var params []interface{}
290 queries = append(queries, "honks.honkid > ?")
291 params = append(params, wanted)
292 queries = append(queries, "honks.userid = ?")
293 params = append(params, userid)
294
295 terms := strings.Split(q, " ")
296 for _, t := range terms {
297 if t == "" {
298 continue
299 }
300 negate := " "
301 if t[0] == '-' {
302 t = t[1:]
303 negate = " not "
304 }
305 if t == "" {
306 continue
307 }
308 if t == "@me" {
309 queries = append(queries, "whofore = 1")
310 continue
311 }
312 if t == "@self" {
313 queries = append(queries, "(whofore = 2 or whofore = 3)")
314 continue
315 }
316 if strings.HasPrefix(t, "before:") {
317 before := t[7:]
318 queries = append(queries, "dt < ?")
319 params = append(params, before)
320 continue
321 }
322 if strings.HasPrefix(t, "after:") {
323 after := t[6:]
324 queries = append(queries, "dt > ?")
325 params = append(params, after)
326 continue
327 }
328 if strings.HasPrefix(t, "site:") {
329 site := t[5:]
330 site = "%" + site + "%"
331 queries = append(queries, "xid"+negate+"like ?")
332 params = append(params, site)
333 continue
334 }
335 if strings.HasPrefix(t, "honker:") {
336 honker := t[7:]
337 xid := fullname(honker, userid)
338 if xid != "" {
339 honker = xid
340 }
341 queries = append(queries, negate+"(honks.honker = ? or honks.oonker = ?)")
342 params = append(params, honker)
343 params = append(params, honker)
344 continue
345 }
346 t = "%" + t + "%"
347 queries = append(queries, "noise"+negate+"like ?")
348 params = append(params, t)
349 }
350
351 selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, format, convoy, whofore, flags from honks join users on honks.userid = users.userid "
352 where := "where " + strings.Join(queries, " and ")
353 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
354 limit := " order by honks.honkid desc limit 250"
355 params = append(params, userid)
356 rows, err := opendatabase().Query(selecthonks+where+butnotthose+limit, params...)
357 honks := getsomehonks(rows, err)
358 return honks
359}
360func gethonksbyontology(userid int64, name string, wanted int64) []*Honk {
361 rows, err := stmtHonksByOntology.Query(wanted, name, userid, userid)
362 honks := getsomehonks(rows, err)
363 return honks
364}
365
366func reversehonks(honks []*Honk) {
367 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
368 honks[i], honks[j] = honks[j], honks[i]
369 }
370}
371
372func getsomehonks(rows *sql.Rows, err error) []*Honk {
373 if err != nil {
374 elog.Printf("error querying honks: %s", err)
375 return nil
376 }
377 defer rows.Close()
378 var honks []*Honk
379 for rows.Next() {
380 h := scanhonk(rows)
381 if h != nil {
382 honks = append(honks, h)
383 }
384 }
385 rows.Close()
386 donksforhonks(honks)
387 return honks
388}
389
390type RowLike interface {
391 Scan(dest ...interface{}) error
392}
393
394func scanhonk(row RowLike) *Honk {
395 h := new(Honk)
396 var dt, aud string
397 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
398 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Format, &h.Convoy, &h.Whofore, &h.Flags)
399 if err != nil {
400 if err != sql.ErrNoRows {
401 elog.Printf("error scanning honk: %s", err)
402 }
403 return nil
404 }
405 h.Date, _ = time.Parse(dbtimeformat, dt)
406 h.Audience = strings.Split(aud, " ")
407 h.Public = loudandproud(h.Audience)
408 return h
409}
410
411func donksforhonks(honks []*Honk) {
412 db := opendatabase()
413 var ids []string
414 hmap := make(map[int64]*Honk)
415 for _, h := range honks {
416 ids = append(ids, fmt.Sprintf("%d", h.ID))
417 hmap[h.ID] = h
418 }
419 idset := strings.Join(ids, ",")
420 // grab donks
421 q := fmt.Sprintf("select honkid, donks.fileid, xid, name, description, url, media, local from donks join filemeta on donks.fileid = filemeta.fileid where honkid in (%s)", idset)
422 rows, err := db.Query(q)
423 if err != nil {
424 elog.Printf("error querying donks: %s", err)
425 return
426 }
427 defer rows.Close()
428 for rows.Next() {
429 var hid int64
430 d := new(Donk)
431 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
432 if err != nil {
433 elog.Printf("error scanning donk: %s", err)
434 continue
435 }
436 d.External = !strings.HasPrefix(d.URL, serverPrefix)
437 h := hmap[hid]
438 h.Donks = append(h.Donks, d)
439 }
440 rows.Close()
441
442 // grab onts
443 q = fmt.Sprintf("select honkid, ontology from onts where honkid in (%s)", idset)
444 rows, err = db.Query(q)
445 if err != nil {
446 elog.Printf("error querying onts: %s", err)
447 return
448 }
449 defer rows.Close()
450 for rows.Next() {
451 var hid int64
452 var o string
453 err = rows.Scan(&hid, &o)
454 if err != nil {
455 elog.Printf("error scanning donk: %s", err)
456 continue
457 }
458 h := hmap[hid]
459 h.Onts = append(h.Onts, o)
460 }
461 rows.Close()
462
463 // grab meta
464 q = fmt.Sprintf("select honkid, genus, json from honkmeta where honkid in (%s)", idset)
465 rows, err = db.Query(q)
466 if err != nil {
467 elog.Printf("error querying honkmeta: %s", err)
468 return
469 }
470 defer rows.Close()
471 for rows.Next() {
472 var hid int64
473 var genus, j string
474 err = rows.Scan(&hid, &genus, &j)
475 if err != nil {
476 elog.Printf("error scanning honkmeta: %s", err)
477 continue
478 }
479 h := hmap[hid]
480 switch genus {
481 case "place":
482 p := new(Place)
483 err = unjsonify(j, p)
484 if err != nil {
485 elog.Printf("error parsing place: %s", err)
486 continue
487 }
488 h.Place = p
489 case "time":
490 t := new(Time)
491 err = unjsonify(j, t)
492 if err != nil {
493 elog.Printf("error parsing time: %s", err)
494 continue
495 }
496 h.Time = t
497 case "mentions":
498 err = unjsonify(j, &h.Mentions)
499 if err != nil {
500 elog.Printf("error parsing mentions: %s", err)
501 continue
502 }
503 case "badonks":
504 err = unjsonify(j, &h.Badonks)
505 if err != nil {
506 elog.Printf("error parsing badonks: %s", err)
507 continue
508 }
509 case "oldrev":
510 default:
511 elog.Printf("unknown meta genus: %s", genus)
512 }
513 }
514 rows.Close()
515}
516
517func donksforchonks(chonks []*Chonk) {
518 db := opendatabase()
519 var ids []string
520 chmap := make(map[int64]*Chonk)
521 for _, ch := range chonks {
522 ids = append(ids, fmt.Sprintf("%d", ch.ID))
523 chmap[ch.ID] = ch
524 }
525 idset := strings.Join(ids, ",")
526 // grab donks
527 q := fmt.Sprintf("select chonkid, donks.fileid, xid, name, description, url, media, local from donks join filemeta on donks.fileid = filemeta.fileid where chonkid in (%s)", idset)
528 rows, err := db.Query(q)
529 if err != nil {
530 elog.Printf("error querying donks: %s", err)
531 return
532 }
533 defer rows.Close()
534 for rows.Next() {
535 var chid int64
536 d := new(Donk)
537 err = rows.Scan(&chid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
538 if err != nil {
539 elog.Printf("error scanning donk: %s", err)
540 continue
541 }
542 ch := chmap[chid]
543 ch.Donks = append(ch.Donks, d)
544 }
545}
546
547func savefile(name string, desc string, url string, media string, local bool, data []byte) (int64, error) {
548 fileid, _, err := savefileandxid(name, desc, url, media, local, data)
549 return fileid, err
550}
551
552func hashfiledata(data []byte) string {
553 h := sha512.New512_256()
554 h.Write(data)
555 return fmt.Sprintf("%x", h.Sum(nil))
556}
557
558func savefileandxid(name string, desc string, url string, media string, local bool, data []byte) (int64, string, error) {
559 var xid string
560 if local {
561 hash := hashfiledata(data)
562 row := stmtCheckFileData.QueryRow(hash)
563 err := row.Scan(&xid)
564 if err == sql.ErrNoRows {
565 xid = xfiltrate()
566 switch media {
567 case "image/png":
568 xid += ".png"
569 case "image/jpeg":
570 xid += ".jpg"
571 case "image/svg+xml":
572 xid += ".svg"
573 case "application/pdf":
574 xid += ".pdf"
575 case "text/plain":
576 xid += ".txt"
577 }
578 _, err = stmtSaveFileData.Exec(xid, media, hash, data)
579 if err != nil {
580 return 0, "", err
581 }
582 } else if err != nil {
583 elog.Printf("error checking file hash: %s", err)
584 return 0, "", err
585 }
586 if url == "" {
587 url = fmt.Sprintf("https://%s/d/%s", serverName, xid)
588 }
589 }
590
591 res, err := stmtSaveFile.Exec(xid, name, desc, url, media, local)
592 if err != nil {
593 return 0, "", err
594 }
595 fileid, _ := res.LastInsertId()
596 return fileid, xid, nil
597}
598
599func finddonkid(fileid int64, url string) *Donk {
600 donk := new(Donk)
601 row := stmtFindFileId.QueryRow(fileid, url)
602 err := row.Scan(&donk.XID, &donk.Local, &donk.Desc)
603 if err == nil {
604 donk.FileID = fileid
605 return donk
606 }
607 if err != sql.ErrNoRows {
608 elog.Printf("error finding file: %s", err)
609 }
610 return nil
611}
612
613func finddonk(url string) *Donk {
614 donk := new(Donk)
615 row := stmtFindFile.QueryRow(url)
616 err := row.Scan(&donk.FileID, &donk.XID)
617 if err == nil {
618 return donk
619 }
620 if err != sql.ErrNoRows {
621 elog.Printf("error finding file: %s", err)
622 }
623 return nil
624}
625
626func savechonk(ch *Chonk) error {
627 dt := ch.Date.UTC().Format(dbtimeformat)
628 db := opendatabase()
629 tx, err := db.Begin()
630 if err != nil {
631 elog.Printf("can't begin tx: %s", err)
632 return err
633 }
634
635 res, err := tx.Stmt(stmtSaveChonk).Exec(ch.UserID, ch.XID, ch.Who, ch.Target, dt, ch.Noise, ch.Format)
636 if err == nil {
637 ch.ID, _ = res.LastInsertId()
638 for _, d := range ch.Donks {
639 _, err := tx.Stmt(stmtSaveDonk).Exec(-1, ch.ID, d.FileID)
640 if err != nil {
641 elog.Printf("error saving donk: %s", err)
642 break
643 }
644 }
645 chatplusone(tx, ch.UserID)
646 err = tx.Commit()
647 } else {
648 tx.Rollback()
649 }
650 return err
651}
652
653func chatplusone(tx *sql.Tx, userid int64) {
654 var user *WhatAbout
655 ok := somenumberedusers.Get(userid, &user)
656 if !ok {
657 return
658 }
659 options := user.Options
660 options.ChatCount += 1
661 j, err := jsonify(options)
662 if err == nil {
663 _, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
664 }
665 if err != nil {
666 elog.Printf("error plussing chat: %s", err)
667 }
668 somenamedusers.Clear(user.Name)
669 somenumberedusers.Clear(user.ID)
670}
671
672func chatnewnone(userid int64) {
673 var user *WhatAbout
674 ok := somenumberedusers.Get(userid, &user)
675 if !ok || user.Options.ChatCount == 0 {
676 return
677 }
678 options := user.Options
679 options.ChatCount = 0
680 j, err := jsonify(options)
681 if err == nil {
682 db := opendatabase()
683 _, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
684 }
685 if err != nil {
686 elog.Printf("error noneing chat: %s", err)
687 }
688 somenamedusers.Clear(user.Name)
689 somenumberedusers.Clear(user.ID)
690}
691
692func meplusone(tx *sql.Tx, userid int64) {
693 var user *WhatAbout
694 ok := somenumberedusers.Get(userid, &user)
695 if !ok {
696 return
697 }
698 options := user.Options
699 options.MeCount += 1
700 j, err := jsonify(options)
701 if err == nil {
702 _, err = tx.Exec("update users set options = ? where username = ?", j, user.Name)
703 }
704 if err != nil {
705 elog.Printf("error plussing me: %s", err)
706 }
707 somenamedusers.Clear(user.Name)
708 somenumberedusers.Clear(user.ID)
709}
710
711func menewnone(userid int64) {
712 var user *WhatAbout
713 ok := somenumberedusers.Get(userid, &user)
714 if !ok || user.Options.MeCount == 0 {
715 return
716 }
717 options := user.Options
718 options.MeCount = 0
719 j, err := jsonify(options)
720 if err == nil {
721 db := opendatabase()
722 _, err = db.Exec("update users set options = ? where username = ?", j, user.Name)
723 }
724 if err != nil {
725 elog.Printf("error noneing me: %s", err)
726 }
727 somenamedusers.Clear(user.Name)
728 somenumberedusers.Clear(user.ID)
729}
730
731func loadchatter(userid int64) []*Chatter {
732 duedt := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
733 rows, err := stmtLoadChonks.Query(userid, duedt)
734 if err != nil {
735 elog.Printf("error loading chonks: %s", err)
736 return nil
737 }
738 defer rows.Close()
739 chonks := make(map[string][]*Chonk)
740 var allchonks []*Chonk
741 for rows.Next() {
742 ch := new(Chonk)
743 var dt string
744 err = rows.Scan(&ch.ID, &ch.UserID, &ch.XID, &ch.Who, &ch.Target, &dt, &ch.Noise, &ch.Format)
745 if err != nil {
746 elog.Printf("error scanning chonk: %s", err)
747 continue
748 }
749 ch.Date, _ = time.Parse(dbtimeformat, dt)
750 chonks[ch.Target] = append(chonks[ch.Target], ch)
751 allchonks = append(allchonks, ch)
752 }
753 donksforchonks(allchonks)
754 rows.Close()
755 rows, err = stmtGetChatters.Query(userid)
756 if err != nil {
757 elog.Printf("error getting chatters: %s", err)
758 return nil
759 }
760 for rows.Next() {
761 var target string
762 err = rows.Scan(&target)
763 if err != nil {
764 elog.Printf("error scanning chatter: %s", target)
765 continue
766 }
767 if _, ok := chonks[target]; !ok {
768 chonks[target] = []*Chonk{}
769
770 }
771 }
772 var chatter []*Chatter
773 for target, chonks := range chonks {
774 chatter = append(chatter, &Chatter{
775 Target: target,
776 Chonks: chonks,
777 })
778 }
779 sort.Slice(chatter, func(i, j int) bool {
780 a, b := chatter[i], chatter[j]
781 if len(a.Chonks) == 0 || len(b.Chonks) == 0 {
782 if len(a.Chonks) == len(b.Chonks) {
783 return a.Target < b.Target
784 }
785 return len(a.Chonks) > len(b.Chonks)
786 }
787 return a.Chonks[len(a.Chonks)-1].Date.After(b.Chonks[len(b.Chonks)-1].Date)
788 })
789
790 return chatter
791}
792
793func savehonk(h *Honk) error {
794 dt := h.Date.UTC().Format(dbtimeformat)
795 aud := strings.Join(h.Audience, " ")
796
797 db := opendatabase()
798 tx, err := db.Begin()
799 if err != nil {
800 elog.Printf("can't begin tx: %s", err)
801 return err
802 }
803
804 res, err := tx.Stmt(stmtSaveHonk).Exec(h.UserID, h.What, h.Honker, h.XID, h.RID, dt, h.URL,
805 aud, h.Noise, h.Convoy, h.Whofore, h.Format, h.Precis,
806 h.Oonker, h.Flags)
807 if err == nil {
808 h.ID, _ = res.LastInsertId()
809 err = saveextras(tx, h)
810 }
811 if err == nil {
812 if h.Whofore == 1 {
813 meplusone(tx, h.UserID)
814 }
815 err = tx.Commit()
816 } else {
817 tx.Rollback()
818 }
819 if err != nil {
820 elog.Printf("error saving honk: %s", err)
821 }
822 honkhonkline()
823 return err
824}
825
826func updatehonk(h *Honk) error {
827 old := getxonk(h.UserID, h.XID)
828 oldrev := OldRevision{Precis: old.Precis, Noise: old.Noise}
829 dt := h.Date.UTC().Format(dbtimeformat)
830
831 db := opendatabase()
832 tx, err := db.Begin()
833 if err != nil {
834 elog.Printf("can't begin tx: %s", err)
835 return err
836 }
837
838 err = deleteextras(tx, h.ID, false)
839 if err == nil {
840 _, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, h.Whofore, dt, h.ID)
841 }
842 if err == nil {
843 err = saveextras(tx, h)
844 }
845 if err == nil {
846 var j string
847 j, err = jsonify(&oldrev)
848 if err == nil {
849 _, err = tx.Stmt(stmtSaveMeta).Exec(old.ID, "oldrev", j)
850 }
851 if err != nil {
852 elog.Printf("error saving oldrev: %s", err)
853 }
854 }
855 if err == nil {
856 err = tx.Commit()
857 } else {
858 tx.Rollback()
859 }
860 if err != nil {
861 elog.Printf("error updating honk %d: %s", h.ID, err)
862 }
863 return err
864}
865
866func deletehonk(honkid int64) error {
867 db := opendatabase()
868 tx, err := db.Begin()
869 if err != nil {
870 elog.Printf("can't begin tx: %s", err)
871 return err
872 }
873
874 err = deleteextras(tx, honkid, true)
875 if err == nil {
876 _, err = tx.Stmt(stmtDeleteHonk).Exec(honkid)
877 }
878 if err == nil {
879 err = tx.Commit()
880 } else {
881 tx.Rollback()
882 }
883 if err != nil {
884 elog.Printf("error deleting honk %d: %s", honkid, err)
885 }
886 return err
887}
888
889func saveextras(tx *sql.Tx, h *Honk) error {
890 for _, d := range h.Donks {
891 _, err := tx.Stmt(stmtSaveDonk).Exec(h.ID, -1, d.FileID)
892 if err != nil {
893 elog.Printf("error saving donk: %s", err)
894 return err
895 }
896 }
897 for _, o := range h.Onts {
898 _, err := tx.Stmt(stmtSaveOnt).Exec(strings.ToLower(o), h.ID)
899 if err != nil {
900 elog.Printf("error saving ont: %s", err)
901 return err
902 }
903 }
904 if p := h.Place; p != nil {
905 j, err := jsonify(p)
906 if err == nil {
907 _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "place", j)
908 }
909 if err != nil {
910 elog.Printf("error saving place: %s", err)
911 return err
912 }
913 }
914 if t := h.Time; t != nil {
915 j, err := jsonify(t)
916 if err == nil {
917 _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "time", j)
918 }
919 if err != nil {
920 elog.Printf("error saving time: %s", err)
921 return err
922 }
923 }
924 if m := h.Mentions; len(m) > 0 {
925 j, err := jsonify(m)
926 if err == nil {
927 _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "mentions", j)
928 }
929 if err != nil {
930 elog.Printf("error saving mentions: %s", err)
931 return err
932 }
933 }
934 return nil
935}
936
937var baxonker sync.Mutex
938
939func addreaction(user *WhatAbout, xid string, who, react string) {
940 baxonker.Lock()
941 defer baxonker.Unlock()
942 h := getxonk(user.ID, xid)
943 if h == nil {
944 return
945 }
946 h.Badonks = append(h.Badonks, Badonk{Who: who, What: react})
947 j, _ := jsonify(h.Badonks)
948 db := opendatabase()
949 tx, _ := db.Begin()
950 _, _ = tx.Stmt(stmtDeleteOneMeta).Exec(h.ID, "badonks")
951 _, _ = tx.Stmt(stmtSaveMeta).Exec(h.ID, "badonks", j)
952 tx.Commit()
953}
954
955func deleteextras(tx *sql.Tx, honkid int64, everything bool) error {
956 _, err := tx.Stmt(stmtDeleteDonks).Exec(honkid)
957 if err != nil {
958 return err
959 }
960 _, err = tx.Stmt(stmtDeleteOnts).Exec(honkid)
961 if err != nil {
962 return err
963 }
964 if everything {
965 _, err = tx.Stmt(stmtDeleteAllMeta).Exec(honkid)
966 } else {
967 _, err = tx.Stmt(stmtDeleteSomeMeta).Exec(honkid)
968 }
969 if err != nil {
970 return err
971 }
972 return nil
973}
974
975func jsonify(what interface{}) (string, error) {
976 var buf bytes.Buffer
977 e := json.NewEncoder(&buf)
978 e.SetEscapeHTML(false)
979 e.SetIndent("", "")
980 err := e.Encode(what)
981 return buf.String(), err
982}
983
984func unjsonify(s string, dest interface{}) error {
985 d := json.NewDecoder(strings.NewReader(s))
986 err := d.Decode(dest)
987 return err
988}
989
990func getxonker(what, flav string) string {
991 var res string
992 row := stmtGetXonker.QueryRow(what, flav)
993 row.Scan(&res)
994 return res
995}
996
997func savexonker(what, value, flav, when string) {
998 stmtSaveXonker.Exec(what, value, flav, when)
999}
1000
1001func savehonker(user *WhatAbout, url, name, flavor, combos, mj string) (int64, error) {
1002 var owner string
1003 if url[0] == '#' {
1004 flavor = "peep"
1005 if name == "" {
1006 name = url[1:]
1007 }
1008 owner = url
1009 } else {
1010 info, err := investigate(url)
1011 if err != nil {
1012 ilog.Printf("failed to investigate honker: %s", err)
1013 return 0, err
1014 }
1015 url = info.XID
1016 if name == "" {
1017 name = info.Name
1018 }
1019 owner = info.Owner
1020 }
1021
1022 var x string
1023 db := opendatabase()
1024 row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, user.ID)
1025 err := row.Scan(&x)
1026 if err != sql.ErrNoRows {
1027 if err != nil {
1028 elog.Printf("honker scan err: %s", err)
1029 } else {
1030 err = fmt.Errorf("it seems you are already subscribed to them")
1031 }
1032 return 0, err
1033 }
1034
1035 res, err := stmtSaveHonker.Exec(user.ID, name, url, flavor, combos, owner, mj)
1036 if err != nil {
1037 elog.Print(err)
1038 return 0, err
1039 }
1040 honkerid, _ := res.LastInsertId()
1041 return honkerid, nil
1042}
1043
1044func cleanupdb(arg string) {
1045 db := opendatabase()
1046 days, err := strconv.Atoi(arg)
1047 var sqlargs []interface{}
1048 var where string
1049 if err != nil {
1050 honker := arg
1051 expdate := time.Now().Add(-3 * 24 * time.Hour).UTC().Format(dbtimeformat)
1052 where = "dt < ? and honker = ?"
1053 sqlargs = append(sqlargs, expdate)
1054 sqlargs = append(sqlargs, honker)
1055 } else {
1056 expdate := time.Now().Add(-time.Duration(days) * 24 * time.Hour).UTC().Format(dbtimeformat)
1057 where = "dt < ? and convoy not in (select convoy from honks where flags & 4 or whofore = 2 or whofore = 3)"
1058 sqlargs = append(sqlargs, expdate)
1059 }
1060 doordie(db, "delete from honks where flags & 4 = 0 and whofore = 0 and "+where, sqlargs...)
1061 doordie(db, "delete from donks where honkid > 0 and honkid not in (select honkid from honks)")
1062 doordie(db, "delete from onts where honkid not in (select honkid from honks)")
1063 doordie(db, "delete from honkmeta where honkid not in (select honkid from honks)")
1064
1065 doordie(db, "delete from filemeta where fileid not in (select fileid from donks)")
1066 for _, u := range allusers() {
1067 doordie(db, "delete from zonkers where userid = ? and wherefore = 'zonvoy' and zonkerid < (select zonkerid from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 1 offset 200)", u.UserID, u.UserID)
1068 }
1069
1070 filexids := make(map[string]bool)
1071 blobdb := openblobdb()
1072 rows, err := blobdb.Query("select xid from filedata")
1073 if err != nil {
1074 elog.Fatal(err)
1075 }
1076 for rows.Next() {
1077 var xid string
1078 err = rows.Scan(&xid)
1079 if err != nil {
1080 elog.Fatal(err)
1081 }
1082 filexids[xid] = true
1083 }
1084 rows.Close()
1085 rows, err = db.Query("select xid from filemeta")
1086 for rows.Next() {
1087 var xid string
1088 err = rows.Scan(&xid)
1089 if err != nil {
1090 elog.Fatal(err)
1091 }
1092 delete(filexids, xid)
1093 }
1094 rows.Close()
1095 tx, err := blobdb.Begin()
1096 if err != nil {
1097 elog.Fatal(err)
1098 }
1099 for xid, _ := range filexids {
1100 _, err = tx.Exec("delete from filedata where xid = ?", xid)
1101 if err != nil {
1102 elog.Fatal(err)
1103 }
1104 }
1105 err = tx.Commit()
1106 if err != nil {
1107 elog.Fatal(err)
1108 }
1109}
1110
1111func getusercount() int {
1112 row := stmtGetUserCount.QueryRow()
1113 var count int
1114 row.Scan(&count)
1115 return count
1116}
1117
1118func getactiveusercount(monthsago int) int {
1119 origin := time.Now().AddDate(0, -monthsago, 0).UTC().Format(dbtimeformat)
1120 row := stmtGetActiveUserCount.QueryRow(origin)
1121
1122 var count int
1123 row.Scan(&count)
1124 return count
1125}
1126
1127func getlocalhonkcount() int {
1128 row := stmtGetLocalHonkCount.QueryRow()
1129
1130 var count int
1131 row.Scan(&count)
1132 return count
1133}
1134
1135var stmtHonkers, stmtDubbers, stmtNamedDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateHonker *sql.Stmt
1136var stmtDeleteHonker *sql.Stmt
1137var stmtAnyXonk, stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
1138var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
1139var stmtHonksFromLongAgo *sql.Stmt
1140var stmtHonksByHonker, stmtSaveHonk, stmtUserByName, stmtUserByNumber *sql.Stmt
1141var stmtEventHonks, stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk *sql.Stmt
1142var stmtFindFile, stmtFindFileId, stmtGetFileData, stmtSaveFileData, stmtSaveFile *sql.Stmt
1143var stmtCheckFileData *sql.Stmt
1144var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt
1145var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
1146var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker, stmtDeleteOldXonkers *sql.Stmt
1147var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
1148var stmtHonksForUserFirstClass *sql.Stmt
1149var stmtSaveMeta, stmtDeleteAllMeta, stmtDeleteOneMeta, stmtDeleteSomeMeta, stmtUpdateHonk *sql.Stmt
1150var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
1151var stmtGetTracks *sql.Stmt
1152var stmtSaveChonk, stmtLoadChonks, stmtGetChatters *sql.Stmt
1153var stmtDeliquentCheck, stmtDeliquentUpdate *sql.Stmt
1154var stmtGetUserCount *sql.Stmt
1155var stmtGetActiveUserCount *sql.Stmt
1156var stmtGetLocalHonkCount *sql.Stmt
1157var stmtSaveMastoApp *sql.Stmt
1158
1159func preparetodie(db *sql.DB, s string) *sql.Stmt {
1160 stmt, err := db.Prepare(s)
1161 if err != nil {
1162 elog.Fatalf("error %s: %s", err, s)
1163 }
1164 return stmt
1165}
1166
1167func prepareStatements(db *sql.DB) {
1168 stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos, meta from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
1169 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, ?, ?, ?, '')")
1170 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ?, folxid = ? where userid = ? and name = ? and xid = ? and flavor = ?")
1171 stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ?, meta = ? where honkerid = ? and userid = ?")
1172 stmtDeleteHonker = preparetodie(db, "delete from honkers where honkerid = ?")
1173 stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?")
1174 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
1175 stmtNamedDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and name = ? and flavor = 'dub'")
1176
1177 selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, format, convoy, whofore, flags from honks join users on honks.userid = users.userid "
1178 limit := " order by honks.honkid desc limit 250"
1179 smalllimit := " order by honks.honkid desc limit ?"
1180 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
1181 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
1182 stmtAnyXonk = preparetodie(db, selecthonks+"where xid = ? and what <> 'bonk' order by honks.honkid asc")
1183 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
1184 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+smalllimit)
1185 stmtEventHonks = preparetodie(db, selecthonks+"where (whofore = 2 or honks.userid = ?) and what = 'event'"+smalllimit)
1186 stmtUserHonks = preparetodie(db, selecthonks+"where honks.honkid > ? and (whofore = 2 or whofore = ?) and username = ? and dt > ?"+smalllimit)
1187 myhonkers := " and honker in (select xid from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'presub') and combos not like '% - %')"
1188 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ?"+myhonkers+butnotthose+limit)
1189 stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and (rid = '' or what = 'bonk')"+myhonkers+butnotthose+limit)
1190 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
1191 stmtHonksFromLongAgo = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and dt < ? and whofore = 2"+butnotthose+limit)
1192 stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")
1193 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.honkid > ? and honks.userid = ? and honkers.name = ?"+butnotthose+limit)
1194 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
1195 stmtHonksByCombo = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and honks.honker in (select xid from honkers where honkers.userid = ? and honkers.combos like ?) "+butnotthose+" union "+selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and honks.userid = ? and onts.ontology in (select xid from honkers where combos like ?)"+butnotthose+limit)
1196 stmtHonksByConvoy = preparetodie(db, selecthonks+"where honks.honkid > ? and (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
1197 stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
1198
1199 stmtSaveMeta = preparetodie(db, "insert into honkmeta (honkid, genus, json) values (?, ?, ?)")
1200 stmtDeleteAllMeta = preparetodie(db, "delete from honkmeta where honkid = ?")
1201 stmtDeleteSomeMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus not in ('oldrev')")
1202 stmtDeleteOneMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus = ?")
1203 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
1204 stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
1205 stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, whofore = ?, dt = ? where honkid = ?")
1206 stmtSaveOnt = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
1207 stmtDeleteOnts = preparetodie(db, "delete from onts where honkid = ?")
1208 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, chonkid, fileid) values (?, ?, ?)")
1209 stmtDeleteDonks = preparetodie(db, "delete from donks where honkid = ?")
1210 stmtSaveFile = preparetodie(db, "insert into filemeta (xid, name, description, url, media, local) values (?, ?, ?, ?, ?, ?)")
1211 blobdb := openblobdb()
1212 stmtSaveFileData = preparetodie(blobdb, "insert into filedata (xid, media, hash, content) values (?, ?, ?, ?)")
1213 stmtCheckFileData = preparetodie(blobdb, "select xid from filedata where hash = ?")
1214 stmtGetFileData = preparetodie(blobdb, "select media, content from filedata where xid = ?")
1215 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
1216 stmtFindFile = preparetodie(db, "select fileid, xid from filemeta where url = ? and local = 1")
1217 stmtFindFileId = preparetodie(db, "select xid, local, description from filemeta where fileid = ? and url = ? and local = 1")
1218 stmtUserByName = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where username = ? and userid > 0")
1219 stmtUserByNumber = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where userid = ?")
1220 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, '', '', '', ?)")
1221 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, userid, rcpt, msg) values (?, ?, ?, ?, ?)")
1222 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
1223 stmtLoadDoover = preparetodie(db, "select tries, userid, rcpt, msg from doovers where dooverid = ?")
1224 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
1225 stmtUntagged = preparetodie(db, "select xid, rid, flags from (select honkid, xid, rid, flags from honks where userid = ? order by honkid desc limit 10000) order by honkid asc")
1226 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
1227 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
1228 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
1229 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
1230 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor, dt) values (?, ?, ?, ?)")
1231 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ? and dt < ?")
1232 stmtDeleteOldXonkers = preparetodie(db, "delete from xonkers where flavor = ? and dt < ?")
1233 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100")
1234 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
1235 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
1236 stmtAllOnts = preparetodie(db, "select ontology, count(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2) group by ontology")
1237 stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
1238 stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)")
1239 stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?")
1240 stmtGetTracks = preparetodie(db, "select fetches from tracks where xid = ?")
1241 stmtSaveChonk = preparetodie(db, "insert into chonks (userid, xid, who, target, dt, noise, format) values (?, ?, ?, ?, ?, ?, ?)")
1242 stmtLoadChonks = preparetodie(db, "select chonkid, userid, xid, who, target, dt, noise, format from chonks where userid = ? and dt > ? order by chonkid asc")
1243 stmtGetChatters = preparetodie(db, "select distinct(target) from chonks where userid = ?")
1244 stmtDeliquentCheck = preparetodie(db, "select dooverid, msg from doovers where userid = ? and rcpt = ?")
1245 stmtDeliquentUpdate = preparetodie(db, "update doovers set msg = ? where dooverid = ?")
1246 stmtGetUserCount = preparetodie(db, "select count(*) from users where userid > 0")
1247 stmtGetActiveUserCount = preparetodie(db, "select count(distinct honker) from honks where whofore = 2 and dt > ?")
1248 stmtGetLocalHonkCount = preparetodie(db, "select count(*) from honks where whofore = 2")
1249
1250 stmtSaveMastoApp = preparetodie(db, "insert into masto (clientname, redirecturis, scopes, clientid, clientsecret, vapidkey) values (?, ?, ?, ?, ?, ?)")
1251}