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}