all repos — honk @ 1cdcf0570ab2ce9ec07863dd7670d1e7e42173e4

my fork of honk

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