a5441f18de
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.
254 lines
5.7 KiB
Go
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
|
|
} |