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