gosora/common/conversations.go
Azareal a5441f18de More conversations progress, still not usable yet.
Flush the debug page incrementally.
Eliminate a tiny bit of code in PreparseMessage.
Shorten some of the pointer capture variables in the poll store.
Tweak the advanced installation instructions for EasyJSON to make it more repeatable.

Added the ConvoKey config setting.
Added /.git/ disk usage to the debug page, if it exists.
Added a UserStore.BulkGetMap multi-user test.
2019-06-16 16:30:58 +10:00

254 lines
5.7 KiB
Go

package common
import (
"io"
"time"
"database/sql"
"encoding/hex"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
qgen "github.com/Azareal/Gosora/query_gen"
)
/*
conversations
conversations_posts
*/
var ConvoPostProcess ConvoPostProcessor = NewDefaultConvoPostProcessor()
type ConvoPostProcessor interface {
OnLoad(co *ConversationPost) (*ConversationPost, error)
OnSave(co *ConversationPost) (*ConversationPost, error)
}
type DefaultConvoPostProcessor struct {
}
func NewDefaultConvoPostProcessor() *DefaultConvoPostProcessor {
return &DefaultConvoPostProcessor{}
}
func (pr *DefaultConvoPostProcessor) OnLoad(co *ConversationPost) (*ConversationPost, error) {
return co, nil
}
func (pr *DefaultConvoPostProcessor) OnSave(co *ConversationPost) (*ConversationPost, error) {
return co, nil
}
type AesConvoPostProcessor struct {
}
func NewAesConvoPostProcessor() *AesConvoPostProcessor {
return &AesConvoPostProcessor{}
}
func (pr *AesConvoPostProcessor) OnLoad(co *ConversationPost) (*ConversationPost, error) {
if co.Post != "aes" {
return co, nil
}
key, _ := hex.DecodeString(Config.ConvoKey)
ciphertext, err := hex.DecodeString(co.Body)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := aesgcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, err
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
lco := *co
lco.Body = string(plaintext)
return &lco, nil
}
func (pr *AesConvoPostProcessor) OnSave(co *ConversationPost) (*ConversationPost, error) {
key, _ := hex.DecodeString(Config.ConvoKey)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
ciphertext := aesgcm.Seal(nil, nonce, []byte(co.Body), nil)
lco := *co
lco.Body = hex.EncodeToString(ciphertext)
lco.Post = "aes"
return &lco, nil
}
var convoStmts ConvoStmts
type ConvoStmts struct {
getPosts *sql.Stmt
edit *sql.Stmt
create *sql.Stmt
editPost *sql.Stmt
createPost *sql.Stmt
}
/*func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
convoStmts = ConvoStmts{
getPosts: acc.Select("conversations_posts").Columns("pid, body, post").Where("cid = ?").Prepare(),
edit: acc.Update("conversations").Set("participants = ?, lastReplyAt = ?").Where("cid = ?").Prepare(),
create: acc.Insert("conversations").Columns("participants, createdAt, lastReplyAt").Fields("?,UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
editPost: acc.Update("conversations_posts").Set("body = ?").Where("cid = ?").Prepare(),
createPost: acc.Insert("conversations_posts").Columns("body").Fields("?").Prepare(),
}
return acc.FirstError()
})
}*/
type Conversation struct {
ID int
Participants string
CreatedAt time.Time
LastReplyAt time.Time
}
func (co *Conversation) Posts(offset int) (posts []*ConversationPost, err error) {
rows, err := convoStmts.getPosts.Query(co.ID, offset, Config.ItemsPerPage)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
convo := &ConversationPost{CID: co.ID}
err := rows.Scan(&convo.ID, &convo.Body, &convo.Post)
if err != nil {
return nil, err
}
convo, err = ConvoPostProcess.OnLoad(convo)
if err != nil {
return nil, err
}
posts = append(posts, convo)
}
err = rows.Err()
if err != nil {
return nil, err
}
return posts, err
}
func (co *Conversation) Update() error {
_, err := convoStmts.edit.Exec(co.Participants, co.CreatedAt, co.LastReplyAt, co.ID)
return err
}
func (co *Conversation) Create() (int, error) {
res, err := convoStmts.create.Exec(co.Participants)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
type ConversationPost struct {
ID int
CID int
Body string
Post string // aes, ''
}
func (co *ConversationPost) Update() error {
lco, err := ConvoPostProcess.OnSave(co)
if err != nil {
return err
}
//GetHookTable().VhookNoRet("convo_post_update", lco)
_, err = convoStmts.editPost.Exec(lco.Body, lco.ID)
return err
}
func (co *ConversationPost) Create() (int, error) {
lco, err := ConvoPostProcess.OnSave(co)
if err != nil {
return 0, err
}
//GetHookTable().VhookNoRet("convo_post_create", lco)
res, err := convoStmts.createPost.Exec(lco.Body)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
type ConversationStore interface {
Get(id int) (*Conversation, error)
Delete(id int) error
Count() (count int)
}
type DefaultConversationStore struct {
get *sql.Stmt
delete *sql.Stmt
count *sql.Stmt
}
func NewDefaultConversationStore(acc *qgen.Accumulator) (*DefaultConversationStore, error) {
return &DefaultConversationStore{
get: acc.Select("conversations").Columns("participants, createdAt, lastReplyAt").Where("cid = ?").Prepare(),
delete: acc.Delete("conversations").Where("cid = ?").Prepare(),
count: acc.Count("conversations").Prepare(),
}, acc.FirstError()
}
func (s *DefaultConversationStore) Get(id int) (*Conversation, error) {
convo := &Conversation{ID: id}
err := s.get.QueryRow(id).Scan(&convo.Participants, &convo.CreatedAt, &convo.LastReplyAt)
return nil, err
}
func (s *DefaultConversationStore) Delete(id int) error {
_, err := s.delete.Exec(id)
return err
}
// Count returns the total number of topics on these forums
func (s *DefaultConversationStore) Count() (count int) {
err := s.count.QueryRow().Scan(&count)
if err != nil {
LogError(err)
}
return count
}