gosora/common/topic_store.go
Azareal 01a692ab5b Added the word filter store and moved the word filter routes into the route package.
Added tests for the word filter store.
Added qgen.NewAcc() to reduce the amount of boilerplate needed for creating an accumulator.
Exposed the RecordError method on the accumulator.
Added an Add method to PluginList and removed AddPlugin() in favour of that.

More panel buttons on Nox should be styled now.
Added the panel_update_button_text phrase for future use.

More errors might be caught in the thumbnailer now.
Removed ls from .travis.yml, it was there for debugging Code Climate.
2018-08-04 21:46:36 +10:00

179 lines
5.8 KiB
Go

/*
*
* Gosora Topic Store
* Copyright Azareal 2017 - 2018
*
*/
package common
import (
"database/sql"
"errors"
"strings"
"../query_gen/lib"
)
// TODO: Add the watchdog goroutine
// TODO: Add BulkGetMap
// TODO: Add some sort of update method
// ? - Should we add stick, lock, unstick, and unlock methods? These might be better on the Topics not the TopicStore
var Topics TopicStore
var ErrNoTitle = errors.New("This message is missing a title")
var ErrLongTitle = errors.New("The title is too long")
var ErrNoBody = errors.New("This message is missing a body")
type TopicStore interface {
DirtyGet(id int) *Topic
Get(id int) (*Topic, error)
BypassGet(id int) (*Topic, error)
Exists(id int) bool
Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error)
AddLastTopic(item *Topic, fid int) error // unimplemented
Reload(id int) error // Too much SQL logic to move into TopicCache
// TODO: Implement these two methods
//Replies(tid int) ([]*Reply, error)
//RepliesRange(tid int, lower int, higher int) ([]*Reply, error)
GlobalCount() int
SetCache(cache TopicCache)
GetCache() TopicCache
}
type DefaultTopicStore struct {
cache TopicCache
get *sql.Stmt
exists *sql.Stmt
topicCount *sql.Stmt
create *sql.Stmt
}
// NewDefaultTopicStore gives you a new instance of DefaultTopicStore
func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
acc := qgen.NewAcc()
if cache == nil {
cache = NewNullTopicCache()
}
return &DefaultTopicStore{
cache: cache,
get: acc.Select("topics").Columns("title, content, createdBy, createdAt, lastReplyAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, poll, data").Where("tid = ?").Prepare(),
exists: acc.Select("topics").Columns("tid").Where("tid = ?").Prepare(),
topicCount: acc.Count("topics").Prepare(),
create: acc.Insert("topics").Columns("parentID, title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, ipaddress, words, createdBy").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?").Prepare(),
}, acc.FirstError()
}
func (mts *DefaultTopicStore) DirtyGet(id int) *Topic {
topic, err := mts.cache.Get(id)
if err == nil {
return topic
}
topic = &Topic{ID: id}
err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
if err == nil {
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
_ = mts.cache.Add(topic)
return topic
}
return BlankTopic()
}
// TODO: Log weird cache errors?
func (mts *DefaultTopicStore) Get(id int) (topic *Topic, err error) {
topic, err = mts.cache.Get(id)
if err == nil {
return topic, nil
}
topic = &Topic{ID: id}
err = mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
if err == nil {
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
_ = mts.cache.Add(topic)
}
return topic, err
}
// BypassGet will always bypass the cache and pull the topic directly from the database
func (mts *DefaultTopicStore) BypassGet(id int) (*Topic, error) {
topic := &Topic{ID: id}
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
return topic, err
}
func (mts *DefaultTopicStore) Reload(id int) error {
topic := &Topic{ID: id}
err := mts.get.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.LastReplyAt, &topic.IsClosed, &topic.Sticky, &topic.ParentID, &topic.IPAddress, &topic.PostCount, &topic.LikeCount, &topic.Poll, &topic.Data)
if err == nil {
topic.Link = BuildTopicURL(NameToSlug(topic.Title), id)
_ = mts.cache.Set(topic)
} else {
_ = mts.cache.Remove(id)
}
return err
}
func (mts *DefaultTopicStore) Exists(id int) bool {
return mts.exists.QueryRow(id).Scan(&id) == nil
}
func (mts *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
if topicName == "" {
return 0, ErrNoTitle
}
// ? This number might be a little screwy with Unicode, but it's the only consistent thing we have, as Unicode characters can be any number of bytes in theory?
if len(topicName) > Config.MaxTopicTitleLength {
return 0, ErrLongTitle
}
parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums"))
if parsedContent == "" {
return 0, ErrNoBody
}
wcount := WordCount(content)
// TODO: Move this statement into the topic store
res, err := mts.create.Exec(fid, topicName, content, parsedContent, uid, ipaddress, wcount, uid)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
}
return int(lastID), Forums.AddTopic(int(lastID), uid, fid)
}
// ? - What is this? Do we need it? Should it be in the main store interface?
func (mts *DefaultTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon...
return nil
}
// GlobalCount returns the total number of topics on these forums
func (mts *DefaultTopicStore) GlobalCount() (tcount int) {
err := mts.topicCount.QueryRow().Scan(&tcount)
if err != nil {
LogError(err)
}
return tcount
}
func (mts *DefaultTopicStore) SetCache(cache TopicCache) {
mts.cache = cache
}
// TODO: We're temporarily doing this so that you can do tcache != nil in getTopicUser. Refactor it.
func (mts *DefaultTopicStore) GetCache() TopicCache {
_, ok := mts.cache.(*NullTopicCache)
if ok {
return nil
}
return mts.cache
}